Fix coverage: add /api/coverage route, remove stray gather code from loadCoverage

This commit is contained in:
Floppyrj45
2026-02-19 14:53:10 +01:00
parent 61b25ab734
commit bbd6a22b57
80 changed files with 27884 additions and 1 deletions

20
Dockerfile.api Normal file
View File

@@ -0,0 +1,20 @@
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
RUN pip install --no-cache-dir \
flask \
flask-cors \
h5py \
numpy
# Copy API server
COPY h5_api_server.py .
# Make executable
RUN chmod +x h5_api_server.py
EXPOSE 3004
CMD ["python", "h5_api_server.py"]

Binary file not shown.

View File

@@ -1628,7 +1628,48 @@ def real_shots():
return jsonify({'error': 'real_shots.json not found'}), 404 return jsonify({'error': 'real_shots.json not found'}), 404
with open(shot_path) as f: with open(shot_path) as f:
return jsonify(json.load(f)) return jsonify(json.load(f))
@app.route("/api/coverage")def get_coverage(): """Get coverage data for all H5 files.""" import re as _re try: darf = {} if os.path.exists(DARF_FILE): with open(DARF_FILE) as _f: darf = json.load(_f) result = [] for f in os.listdir(DATA_DIR): if not f.endswith(".h5"): continue fpath = os.path.join(DATA_DIR, f) info = {"name": f, "size": os.path.getsize(fpath)} m = _re.match(r"auto_(d+)_(d{2})(d{2})(d{2})_b(d+)_rsn(d+)_seq(d+)_(d+)", f) if m: day, hh, mm, ss, board_id, rsn, seq, ts = m.groups() info["board_id"] = int(board_id) info["day"] = int(day) from datetime import datetime as _dt, timedelta as _td base = _dt(2020, 1, 1) + _td(days=int(day)-1, hours=int(hh), minutes=int(mm), seconds=int(ss)) info["date_str"] = base.isoformat() bid_str = str(board_id) if bid_str in darf: info["line"] = darf[bid_str].get("line") info["point"] = darf[bid_str].get("point") info["duration_sec"] = 8000 try: import h5py with h5py.File(fpath, "r") as h5f: attrs = dict(h5f.attrs) info["duration_sec"] = float(attrs.get("duration", attrs.get("record_length", 8000))) except: pass result.append(info) result.sort(key=lambda x: x.get("date_str", "")) return jsonify({"files": result, "count": len(result)}) except Exception as e: return jsonify({"error": str(e)}), 500
@app.route('/api/coverage')
def get_coverage():
"""Get coverage data for all H5 files."""
import re as _re
try:
darf = {}
if os.path.exists(DARF_FILE):
with open(DARF_FILE) as _f:
darf = json.load(_f)
result = []
for f in os.listdir(DATA_DIR):
if not f.endswith('.h5'):
continue
fpath = os.path.join(DATA_DIR, f)
info = {'name': f, 'size': os.path.getsize(fpath)}
m = _re.match(r'auto_(\d+)_(\d{2})(\d{2})(\d{2})_b(\d+)_rsn(\d+)_seq(\d+)_(\d+)', f)
if m:
day, hh, mm, ss, board_id, rsn, seq, ts = m.groups()
info['board_id'] = int(board_id)
info['day'] = int(day)
from datetime import datetime as _dt, timedelta as _td
base = _dt(2020, 1, 1) + _td(days=int(day)-1, hours=int(hh), minutes=int(mm), seconds=int(ss))
info['date_str'] = base.isoformat()
bid_str = str(board_id)
if bid_str in darf:
info['line'] = darf[bid_str].get('line')
info['point'] = darf[bid_str].get('point')
info['duration_sec'] = 8000
try:
import h5py
with h5py.File(fpath, 'r') as h5f:
attrs = dict(h5f.attrs)
info['duration_sec'] = float(attrs.get('duration', attrs.get('record_length', 8000)))
except:
pass
result.append(info)
result.sort(key=lambda x: x.get('date_str', ''))
return jsonify({'files': result, 'count': len(result)})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=3001, debug=False) app.run(host='0.0.0.0', port=3001, debug=False)

927
app.py.fixed Normal file
View File

@@ -0,0 +1,927 @@
from flask import Flask, jsonify, send_from_directory, request, send_file
from flask_cors import CORS
import os
import re
import h5py
import numpy as np
from datetime import datetime, timedelta
import base64
import struct
import traceback
import json
import io
import mimetypes
from pyproj import Transformer
import shutil
app = Flask(__name__, static_folder='static', static_url_path='')
CORS(app)
DATA_DIR = os.environ.get('DATA_DIR', '/data')
LOCATIONS_FILE = '/home/floppyrj45/seismic-viewer/locations.json'
# ========== UTILITY FUNCTIONS ==========
def load_locations():
"""Load locations from JSON file."""
if os.path.exists(LOCATIONS_FILE):
try:
with open(LOCATIONS_FILE, 'r') as f:
return json.load(f)
except:
return {}
return {}
def save_locations(locations):
"""Save locations to JSON file."""
with open(LOCATIONS_FILE, 'w') as f:
json.dump(locations, f, indent=2)
def get_h5_metadata(filepath):
"""Extract comprehensive metadata from H5 file."""
metadata = {}
try:
with h5py.File(filepath, 'r') as f:
# File-level attributes
for k, v in f.attrs.items():
try:
metadata[k] = v.item() if hasattr(v, 'item') else str(v)
except:
metadata[k] = str(v)
# Metadata group attributes
if 'metadata' in f:
for k, v in f['metadata'].attrs.items():
try:
metadata[k] = v.item() if hasattr(v, 'item') else str(v)
except:
metadata[k] = str(v)
# Calibration group attributes
if 'calibration' in f:
calibration = {}
for k, v in f['calibration'].attrs.items():
try:
calibration[k] = v.item() if hasattr(v, 'item') else str(v)
except:
calibration[k] = str(v)
metadata['calibration'] = calibration
# Channel information
channels = []
for group_name in ['calibrated_data', 'raw_data']:
if group_name in f:
for ds_name in f[group_name].keys():
ds = f[group_name][ds_name]
ch_info = {
'name': f"{group_name}/{ds_name}",
'shape': list(ds.shape),
'dtype': str(ds.dtype),
'samples': int(ds.shape[0]) if len(ds.shape) > 0 else 0
}
for k, v in ds.attrs.items():
try:
ch_info[k] = v.item() if hasattr(v, 'item') else str(v)
except:
ch_info[k] = str(v)
channels.append(ch_info)
metadata['channels'] = channels
# Compute timestamps
if 'creation_date' in metadata:
try:
start_time = datetime.fromisoformat(metadata['creation_date'].replace('Z', '+00:00'))
metadata['start_timestamp'] = start_time.isoformat()
if 'duration_sec' in metadata:
duration_sec = float(metadata['duration_sec'])
end_time = start_time + timedelta(seconds=duration_sec)
metadata['end_timestamp'] = end_time.isoformat()
# Human-readable duration
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
seconds = int(duration_sec % 60)
metadata['duration_human'] = f"{hours}h {minutes}m {seconds}s"
except Exception as e:
metadata['timestamp_error'] = str(e)
return metadata
except Exception as e:
return {'error': str(e)}
# ========== ROUTES ==========
@app.route('/')
def index():
return send_from_directory('static', 'index.html')
@app.route('/api/files')
def list_files():
try:
files = []
for fn in os.listdir(DATA_DIR):
if fn.endswith(('.segy', '.sgy', '.h5')):
fp = os.path.join(DATA_DIR, fn)
stat = os.stat(fp)
ftype = 'h5' if fn.endswith('.h5') else 'segy'
files.append({
'name': fn, 'size': stat.st_size,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'type': ftype
})
files.sort(key=lambda x: x['name'])
return jsonify({'files': files, 'count': len(files)})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/info')
def file_info(filename):
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
stat = os.stat(fp)
info = {
'name': filename,
'size': stat.st_size,
'size_mb': round(stat.st_size / 1048576, 2),
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'type': 'h5' if filename.endswith('.h5') else 'segy',
}
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
datasets = []
def visitor(name, obj):
if isinstance(obj, h5py.Dataset):
datasets.append({
'path': name,
'shape': list(obj.shape),
'dtype': str(obj.dtype),
'samples': int(obj.shape[0]) if len(obj.shape) > 0 else 0
})
f.visititems(visitor)
info['datasets'] = datasets
info['num_datasets'] = len(datasets)
cal = [d for d in datasets if 'calibrated' in d['path']]
raw = [d for d in datasets if 'raw' in d['path']]
info['calibrated_channels'] = len(cal)
info['raw_channels'] = len(raw)
if cal:
info['samples_per_channel'] = cal[0]['samples']
# Add sample rate and duration for sidebar display
if 'metadata' in f:
sample_rate = f['metadata'].attrs.get('sample_rate_hz', None)
if sample_rate and cal:
total_samples = cal[0]['samples']
duration_sec = float(total_samples) / float(sample_rate)
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
info['duration_human'] = f"{hours}h{minutes:02d}m"
info['sample_rate_hz'] = int(sample_rate)
info['num_channels'] = int(len(cal))
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp, headonly=True)
info['num_traces'] = len(st)
if len(st) > 0:
info['samples_per_trace'] = st[0].stats.npts
info['sample_rate'] = st[0].stats.sampling_rate
info['delta'] = st[0].stats.delta
except Exception as e:
info['segy_error'] = str(e)
return jsonify(info)
except Exception as e:
return jsonify({'error': str(e)}), 500
# ========== NEW: METADATA ENDPOINT ==========
@app.route('/api/file/<filename>/metadata')
def file_metadata(filename):
"""Return detailed metadata for a file."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
stat = os.stat(fp)
result = {
'filename': filename,
'file_size_bytes': stat.st_size,
'file_size_mb': round(stat.st_size / 1048576, 2),
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
}
if filename.endswith('.h5'):
metadata = get_h5_metadata(fp)
result.update(metadata)
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp, headonly=True)
result['num_traces'] = len(st)
if len(st) > 0:
result['sample_rate'] = st[0].stats.sampling_rate
result['samples_per_trace'] = st[0].stats.npts
result['start_timestamp'] = st[0].stats.starttime.isoformat()
result['end_timestamp'] = st[0].stats.endtime.isoformat()
duration_sec = (st[0].stats.endtime - st[0].stats.starttime)
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
seconds = int(duration_sec % 60)
result['duration_human'] = f"{hours}h {minutes}m {seconds}s"
result['duration_sec'] = duration_sec
except Exception as e:
result['segy_error'] = str(e)
return jsonify(result)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== NEW: LOCATIONS ENDPOINT ==========
@app.route('/api/locations', methods=['GET'])
def get_locations():
"""Get all file locations."""
try:
locations = load_locations()
return jsonify(locations)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/locations', methods=['POST'])
def update_location():
"""Update location for a file."""
try:
data = request.json
filename = data.get('filename')
lat = data.get('lat')
lon = data.get('lon')
if not all([filename, lat is not None, lon is not None]):
return jsonify({'error': 'Missing filename, lat, or lon'}), 400
locations = load_locations()
locations[filename] = {
'lat': float(lat),
'lon': float(lon),
'updated': datetime.now().isoformat()
}
save_locations(locations)
return jsonify({'success': True, 'locations': locations})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== NEW: EXTRACT ENDPOINT ==========
@app.route('/api/file/<filename>/extract')
def extract_segment(filename):
"""Extract a time segment from a file and return as CSV or H5."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
# Parameters
start_sec = float(request.args.get('start_sec', 0))
duration_sec = float(request.args.get('duration_sec', 1200)) # Default 20 min
channel = request.args.get('channel', 'calibrated_data/channel_1')
format_type = request.args.get('format', 'csv') # csv or h5
if not filename.endswith('.h5'):
return jsonify({'error': 'Extract only supported for H5 files'}), 400
with h5py.File(fp, 'r') as f:
if channel not in f:
return jsonify({'error': f'Channel {channel} not found'}), 404
# Get metadata
sample_rate = f['metadata'].attrs.get('sample_rate_hz', 500)
total_samples = f[channel].shape[0]
# Calculate sample range
start_sample = int(start_sec * sample_rate)
end_sample = int((start_sec + duration_sec) * sample_rate)
# Clamp to valid range
start_sample = max(0, min(start_sample, total_samples - 1))
end_sample = max(start_sample + 1, min(end_sample, total_samples))
# Extract data
data = f[channel][start_sample:end_sample]
if format_type == 'csv':
# Generate CSV
output = io.StringIO()
output.write(f"# Channel: {channel}\n")
output.write(f"# Start: {start_sec}s, Duration: {duration_sec}s\n")
output.write(f"# Sample rate: {sample_rate} Hz\n")
output.write(f"# Samples: {len(data)}\n")
output.write("sample,time_sec,value\n")
for i, val in enumerate(data):
time_s = (start_sample + i) / sample_rate
output.write(f"{start_sample + i},{time_s:.6f},{val}\n")
output.seek(0)
return send_file(
io.BytesIO(output.getvalue().encode('utf-8')),
mimetype='text/csv',
as_attachment=True,
download_name=f'{filename.replace(".h5", "")}_extract_{int(start_sec)}_{int(duration_sec)}s.csv'
)
elif format_type == 'h5':
# Generate H5
output = io.BytesIO()
with h5py.File(output, 'w') as out_f:
# Copy metadata
if 'metadata' in f:
meta_group = out_f.create_group('metadata')
for k, v in f['metadata'].attrs.items():
meta_group.attrs[k] = v
meta_group.attrs['extract_start_sec'] = start_sec
meta_group.attrs['extract_duration_sec'] = duration_sec
meta_group.attrs['extract_start_sample'] = start_sample
meta_group.attrs['extract_end_sample'] = end_sample
# Create dataset
ds = out_f.create_dataset('data', data=data, compression='gzip')
ds.attrs['channel'] = channel
ds.attrs['sample_rate_hz'] = sample_rate
# Copy channel attributes
if channel in f:
for k, v in f[channel].attrs.items():
ds.attrs[k] = v
output.seek(0)
return send_file(
output,
mimetype='application/x-hdf5',
as_attachment=True,
download_name=f'{filename.replace(".h5", "")}_extract_{int(start_sec)}_{int(duration_sec)}s.h5'
)
else:
return jsonify({'error': 'Invalid format. Use csv or h5'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== EXISTING ENDPOINTS (kept for compatibility) ==========
@app.route('/api/file/<filename>/section')
def file_section(filename):
"""Return 2D section data as base64 float32 array."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
trace_start = int(request.args.get('trace_start', 0))
trace_count = int(request.args.get('trace_count', 200))
samples_per_trace = int(request.args.get('samples_per_trace', 1000))
channel = request.args.get('channel', 'calibrated_data/channel_1')
max_samples = int(request.args.get('max_samples', 500))
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
if channel not in f:
avail = []
f.visititems(lambda n, o: avail.append(n) if isinstance(o, h5py.Dataset) else None)
return jsonify({'error': f'Channel not found', 'available': avail}), 404
data = f[channel]
total_samples = data.shape[0]
total_traces = total_samples // samples_per_trace
trace_start = max(0, min(trace_start, max(0, total_traces - 1)))
trace_end = min(trace_start + trace_count, total_traces)
actual_count = trace_end - trace_start
if actual_count <= 0:
return jsonify({'error': 'No traces in range'}), 400
start_sample = trace_start * samples_per_trace
end_sample = trace_end * samples_per_trace
raw = data[start_sample:end_sample]
section = raw.reshape(actual_count, samples_per_trace).astype(np.float32)
if samples_per_trace > max_samples:
factor = samples_per_trace // max_samples
trimmed = section[:, :factor * max_samples]
section = trimmed.reshape(actual_count, max_samples, factor).mean(axis=2).astype(np.float32)
actual_spt = max_samples
else:
actual_spt = samples_per_trace
vmin = float(np.min(section))
vmax = float(np.max(section))
encoded = base64.b64encode(section.tobytes()).decode('ascii')
return jsonify({
'num_traces': int(actual_count),
'samples_per_trace': actual_spt,
'trace_start': trace_start,
'total_traces': total_traces,
'total_samples': int(total_samples),
'vmin': vmin, 'vmax': vmax,
'data_b64': encoded,
'dtype': 'float32'
})
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp)
total_traces = len(st)
trace_start = max(0, min(trace_start, total_traces - 1))
trace_end = min(trace_start + trace_count, total_traces)
actual_count = trace_end - trace_start
if actual_count <= 0:
return jsonify({'error': 'No traces'}), 400
spt = st[0].stats.npts
actual_spt = spt
section = np.zeros((actual_count, spt), dtype=np.float32)
for i in range(actual_count):
tr = st[trace_start + i]
n = min(spt, tr.stats.npts)
section[i, :n] = tr.data[:n].astype(np.float32)
if spt > max_samples:
factor = spt // max_samples
trimmed = section[:, :factor * max_samples]
section = trimmed.reshape(actual_count, max_samples, factor).mean(axis=2).astype(np.float32)
actual_spt = max_samples
vmin = float(np.min(section))
vmax = float(np.max(section))
encoded = base64.b64encode(section.tobytes()).decode('ascii')
return jsonify({
'num_traces': int(actual_count),
'samples_per_trace': actual_spt,
'trace_start': trace_start,
'total_traces': total_traces,
'total_samples': int(total_traces * spt),
'vmin': vmin, 'vmax': vmax,
'data_b64': encoded,
'dtype': 'float32'
})
except Exception as e:
return jsonify({'error': f'SEGY read error: {e}'}), 500
return jsonify({'error': 'Unsupported format'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/headers')
def file_headers(filename):
"""Return trace headers for SEGY or dataset attributes for H5."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
attrs = {}
for k, v in f.attrs.items():
try:
attrs[k] = v.item() if hasattr(v, 'item') else str(v)
except:
attrs[k] = str(v)
datasets = []
def visitor(name, obj):
if isinstance(obj, h5py.Dataset):
ds_info = {
'path': name,
'shape': list(obj.shape),
'dtype': str(obj.dtype),
'size': int(np.prod(obj.shape)),
}
for ak, av in obj.attrs.items():
try:
ds_info[f'attr:{ak}'] = av.item() if hasattr(av, 'item') else str(av)
except:
ds_info[f'attr:{ak}'] = str(av)
datasets.append(ds_info)
f.visititems(visitor)
groups = []
def gvisitor(name, obj):
if isinstance(obj, h5py.Group):
g_info = {'path': name}
for ak, av in obj.attrs.items():
try:
g_info[f'attr:{ak}'] = av.item() if hasattr(av, 'item') else str(av)
except:
g_info[f'attr:{ak}'] = str(av)
if len(g_info) > 1:
groups.append(g_info)
f.visititems(gvisitor)
return jsonify({
'type': 'h5',
'file_attrs': attrs,
'datasets': datasets,
'groups': groups
})
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
from obspy.io.segy.segy import _read_segy
segy = _read_segy(fp)
bfh = {}
if hasattr(segy, 'binary_file_header'):
h = segy.binary_file_header
for attr in ['job_identification_number', 'line_number', 'reel_number',
'number_of_data_traces_per_ensemble', 'number_of_auxiliary_traces_per_ensemble',
'sample_interval_in_microseconds', 'number_of_samples_per_data_trace',
'data_sample_format_code']:
if hasattr(h, attr):
bfh[attr] = getattr(h, attr)
trace_headers = []
max_traces = min(50, len(segy.traces))
for i in range(max_traces):
th = segy.traces[i].header
trace_headers.append({
'trace': i,
'inline': getattr(th, 'trace_sequence_number_within_line', None),
'crossline': getattr(th, 'trace_sequence_number_within_segy_file', None),
'field_record': getattr(th, 'original_field_record_number', None),
'trace_number': getattr(th, 'trace_number_within_the_original_field_record', None),
'cdp': getattr(th, 'ensemble_number', None),
'trace_in_ensemble': getattr(th, 'trace_number_within_the_ensemble', None),
'offset': getattr(th, 'distance_from_center_of_the_source_point_to_the_center_of_the_receiver_group', None),
'source_x': getattr(th, 'source_coordinate_x', None),
'source_y': getattr(th, 'source_coordinate_y', None),
'group_x': getattr(th, 'group_coordinate_x', None),
'group_y': getattr(th, 'group_coordinate_y', None),
'num_samples': getattr(th, 'number_of_samples_in_this_trace', None),
'sample_interval': getattr(th, 'sample_interval_in_ms_for_this_trace', None),
})
return jsonify({
'type': 'segy',
'binary_header': bfh,
'trace_headers': trace_headers,
'total_traces': len(segy.traces)
})
except Exception as e:
return jsonify({'error': f'SEGY header read error: {e}'}), 500
return jsonify({'error': 'Unsupported'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/waveform')
def file_waveform(filename):
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
max_pts = int(request.args.get('points', 5000))
channel = request.args.get('channel', 'calibrated_data/channel_1')
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
if channel not in f:
return jsonify({'error': 'Channel not found'}), 404
data = f[channel][:]
n = len(data)
if n <= max_pts:
values = data.tolist()
indices = list(range(n))
else:
step = n // max_pts
indices = list(range(0, n, step))[:max_pts]
values = [float(data[i]) for i in indices]
return jsonify({
'name': filename, 'channel': channel,
'total_samples': n,
'displayed_points': len(values),
'min': float(np.min(data)), 'max': float(np.max(data)),
'mean': float(np.mean(data)), 'std': float(np.std(data)),
'x': indices, 'y': values
})
return jsonify({'error': 'Waveform only for H5'}), 400
except Exception as e:
return jsonify({'error': str(e)}), 500
# ========== GEOSUP ENDPOINTS ==========
# UTM Zone 31N to WGS84 transformer
utm_to_wgs84 = Transformer.from_crs("EPSG:32631", "EPSG:4326", always_xy=True)
GEOSUP_DIR = os.path.join(app.static_folder, '') # Static folder
def parse_sps_line(line):
"""Parse SPS S01/R01 format line. Fixed-width format."""
if len(line) < 65:
return None
try:
record_type = line[0].strip() # S or R
line_num = line[1:11].strip()
point = line[11:21].strip()
index = line[21:23].strip()
# Easting and Northing are in columns ~26-52
coord_part = line[46:66].strip()
# Parse XXXXXX.XNNNNNNN.NN format
match = re.match(r'(\d{5,6}\.\d{1,2})(\d{7}\.\d{1,2})', coord_part)
if match:
easting = float(match.group(1))
northing = float(match.group(2))
else:
parts = coord_part.split('.')
if len(parts) >= 2:
easting_int = parts[0]
rest = '.'.join(parts[1:])
if len(rest) > 9:
easting = float(easting_int + '.' + rest[:2])
northing = float(rest[2:])
else:
return None
else:
return None
depth = line[66:72].strip() if len(line) > 52 else '0'
depth = float(depth) if depth else 0
return {
'type': record_type,
'line': line_num,
'point': point,
'index': index,
'easting': easting,
'northing': northing,
'depth': depth
}
except Exception:
return None
def parse_deployment_csv(filepath):
"""Parse deployment CSV file (SETE-0965P1002.csv format)."""
records = []
try:
with open(filepath, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 8:
try:
record = {
'line': parts[0],
'point': parts[1],
'index': parts[2],
'name': parts[3],
'easting': float(parts[5]),
'northing': float(parts[6]),
'depth': float(parts[7]),
}
if len(parts) >= 16:
record['year'] = int(parts[10])
record['day'] = int(parts[11])
record['hour'] = int(parts[12])
record['minute'] = int(parts[13])
record['second'] = int(parts[14])
records.append(record)
except (ValueError, IndexError):
continue
except Exception:
pass
return records
@app.route('/api/geosup/positions')
def geosup_positions():
"""Return GeoJSON with source, receiver, and deployment positions."""
try:
features = []
# Parse sources (S01)
s01_path = os.path.join(GEOSUP_DIR, 'SeteSxPreplots.s01')
if os.path.exists(s01_path):
with open(s01_path, 'r') as f:
for line in f:
record = parse_sps_line(line)
if record and record['type'] == 'S':
lon, lat = utm_to_wgs84.transform(record['easting'], record['northing'])
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': {
'category': 'source',
'line': record['line'],
'point': record['point'],
'easting': record['easting'],
'northing': record['northing'],
'depth': record['depth']
}
})
# Parse receivers (R01)
r01_path = os.path.join(GEOSUP_DIR, 'SeteRxPreplots.r01')
if os.path.exists(r01_path):
with open(r01_path, 'r') as f:
for line in f:
record = parse_sps_line(line)
if record and record['type'] == 'R':
lon, lat = utm_to_wgs84.transform(record['easting'], record['northing'])
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': {
'category': 'receiver',
'line': record['line'],
'point': record['point'],
'easting': record['easting'],
'northing': record['northing'],
'depth': record['depth']
}
})
# Parse deployments (CSV)
csv_path = os.path.join(GEOSUP_DIR, 'SETE-0965P1002.csv')
if os.path.exists(csv_path):
deployments = parse_deployment_csv(csv_path)
for d in deployments:
lon, lat = utm_to_wgs84.transform(d['easting'], d['northing'])
props = {
'category': 'deployment',
'line': d['line'],
'point': d['point'],
'name': d['name'],
'easting': d['easting'],
'northing': d['northing'],
'depth': d['depth']
}
if 'year' in d:
props['timestamp'] = f"{d['year']}-{d['day']:03d} {d['hour']:02d}:{d['minute']:02d}:{d['second']:02d}"
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': props
})
sources = len([f for f in features if f['properties']['category'] == 'source'])
receivers = len([f for f in features if f['properties']['category'] == 'receiver'])
deployments = len([f for f in features if f['properties']['category'] == 'deployment'])
return jsonify({
'type': 'FeatureCollection',
'features': features,
'counts': {'sources': sources, 'receivers': receivers, 'deployments': deployments, 'total': len(features)}
})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/documents')
def geosup_documents():
"""List all geosup documents in static folder."""
try:
documents = []
doc_extensions = ('.pdf', '.docx', '.xlsx', '.doc', '.xls')
for fn in os.listdir(GEOSUP_DIR):
if fn.lower().endswith(doc_extensions):
fp = os.path.join(GEOSUP_DIR, fn)
stat = os.stat(fp)
ext = os.path.splitext(fn)[1].lower()
doc_type = {'.pdf': 'PDF', '.docx': 'Word', '.doc': 'Word', '.xlsx': 'Excel', '.xls': 'Excel'}.get(ext, 'Document')
icon = {'.pdf': '📕', '.docx': '📘', '.doc': '📘', '.xlsx': '📗', '.xls': '📗'}.get(ext, '📄')
is_gundalf = 'GUNDALF' in fn.upper()
is_params = 'Acquisition_Parameters' in fn or 'SpiceRack' in fn
documents.append({
'name': fn,
'size': stat.st_size,
'size_human': f"{stat.st_size / 1024:.1f} KB" if stat.st_size < 1048576 else f"{stat.st_size / 1048576:.1f} MB",
'type': doc_type,
'icon': icon,
'is_gundalf': is_gundalf,
'is_params': is_params,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat()
})
documents.sort(key=lambda d: (not d['is_gundalf'], not d['is_params'], d['name']))
return jsonify({'documents': documents, 'count': len(documents)})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/documents/<path:filename>')
def geosup_download(filename):
"""Download a geosup document."""
try:
safe_name = os.path.basename(filename)
filepath = os.path.join(GEOSUP_DIR, safe_name)
if not os.path.exists(filepath):
return jsonify({'error': 'File not found'}), 404
mime_type, _ = mimetypes.guess_type(filepath)
return send_file(filepath, mimetype=mime_type or 'application/octet-stream', as_attachment=True, download_name=safe_name)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/acquisition-params')
def geosup_acquisition_params():
"""Extract key parameters from the acquisition spreadsheet."""
try:
xlsx_path = os.path.join(GEOSUP_DIR, 'SBGS_Sete_SpiceRack_Acquisition_Parameters_Spreadsheet.xlsx')
if not os.path.exists(xlsx_path):
return jsonify({'error': 'Acquisition parameters file not found'}), 404
from openpyxl import load_workbook
wb = load_workbook(xlsx_path, data_only=True)
params = {'sheets': [], 'summary': {}}
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
sheet_data = {'name': sheet_name, 'rows': []}
row_count = 0
for row in ws.iter_rows(max_row=30, values_only=True):
if any(cell is not None for cell in row):
sheet_data['rows'].append([str(cell) if cell is not None else '' for cell in row[:10]])
row_count += 1
if row_count >= 20:
break
params['sheets'].append(sheet_data)
wb.close()
return jsonify(params)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/pipeline-status')
def pipeline_status():
"""Return pipeline statistics and disk usage."""
try:
raw_dir = '/mnt/usb/Recordings'
output_dir = '/mnt/usb/H5-Output'
# Count files
raw_count = 0
if os.path.exists(raw_dir):
raw_count = len([n for n in os.listdir(raw_dir) if n.lower().endswith(('.raw', '.manta'))])
segy_count = 0
h5_count = 0
if os.path.exists(output_dir):
files = os.listdir(output_dir)
segy_count = len([n for n in files if n.lower().endswith(('.segy', '.sgy'))])
h5_count = len([n for n in files if n.lower().endswith('.h5')])
# Disk usage
total, used, free = shutil.disk_usage(output_dir)
# Conversion progress
progress = None
if os.path.exists('/tmp/segy2h5_progress.json'):
try:
with open('/tmp/segy2h5_progress.json', 'r') as f:
progress = json.load(f)
except:
pass
return jsonify({
'counts': {
'raw': raw_count,
'segy': segy_count,
'h5': h5_count,
'total_expected': 345 # Hardcoded target from context
},
'disk': {
'total_gb': round(total / (1024**3), 2),
'used_gb': round(used / (1024**3), 2),
'free_gb': round(free / (1024**3), 2),
'percent': round((used / total) * 100, 1)
},
'progress': progress
})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3001, debug=False)
@app.route('/test-map')
def test_map():
return send_from_directory('static', 'test-map.html')

927
app.py.original Normal file
View File

@@ -0,0 +1,927 @@
from flask import Flask, jsonify, send_from_directory, request, send_file
from flask_cors import CORS
import os
import re
import h5py
import numpy as np
from datetime import datetime, timedelta
import base64
import struct
import traceback
import json
import io
import mimetypes
from pyproj import Transformer
import shutil
app = Flask(__name__, static_folder='static', static_url_path='')
CORS(app)
DATA_DIR = os.environ.get('DATA_DIR', '/data')
LOCATIONS_FILE = '/home/floppyrj45/seismic-viewer/locations.json'
# ========== UTILITY FUNCTIONS ==========
def load_locations():
"""Load locations from JSON file."""
if os.path.exists(LOCATIONS_FILE):
try:
with open(LOCATIONS_FILE, 'r') as f:
return json.load(f)
except:
return {}
return {}
def save_locations(locations):
"""Save locations to JSON file."""
with open(LOCATIONS_FILE, 'w') as f:
json.dump(locations, f, indent=2)
def get_h5_metadata(filepath):
"""Extract comprehensive metadata from H5 file."""
metadata = {}
try:
with h5py.File(filepath, 'r') as f:
# File-level attributes
for k, v in f.attrs.items():
try:
metadata[k] = v.item() if hasattr(v, 'item') else str(v)
except:
metadata[k] = str(v)
# Metadata group attributes
if 'metadata' in f:
for k, v in f['metadata'].attrs.items():
try:
metadata[k] = v.item() if hasattr(v, 'item') else str(v)
except:
metadata[k] = str(v)
# Calibration group attributes
if 'calibration' in f:
calibration = {}
for k, v in f['calibration'].attrs.items():
try:
calibration[k] = v.item() if hasattr(v, 'item') else str(v)
except:
calibration[k] = str(v)
metadata['calibration'] = calibration
# Channel information
channels = []
for group_name in ['calibrated_data', 'raw_data']:
if group_name in f:
for ds_name in f[group_name].keys():
ds = f[group_name][ds_name]
ch_info = {
'name': f"{group_name}/{ds_name}",
'shape': list(ds.shape),
'dtype': str(ds.dtype),
'samples': int(ds.shape[0]) if len(ds.shape) > 0 else 0
}
for k, v in ds.attrs.items():
try:
ch_info[k] = v.item() if hasattr(v, 'item') else str(v)
except:
ch_info[k] = str(v)
channels.append(ch_info)
metadata['channels'] = channels
# Compute timestamps
if 'creation_date' in metadata:
try:
start_time = datetime.fromisoformat(metadata['creation_date'].replace('Z', '+00:00'))
metadata['start_timestamp'] = start_time.isoformat()
if 'duration_sec' in metadata:
duration_sec = float(metadata['duration_sec'])
end_time = start_time + timedelta(seconds=duration_sec)
metadata['end_timestamp'] = end_time.isoformat()
# Human-readable duration
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
seconds = int(duration_sec % 60)
metadata['duration_human'] = f"{hours}h {minutes}m {seconds}s"
except Exception as e:
metadata['timestamp_error'] = str(e)
return metadata
except Exception as e:
return {'error': str(e)}
# ========== ROUTES ==========
@app.route('/')
def index():
return send_from_directory('static', 'index.html')
@app.route('/api/files')
def list_files():
try:
files = []
for fn in os.listdir(DATA_DIR):
if fn.endswith(('.segy', '.sgy', '.h5')):
fp = os.path.join(DATA_DIR, fn)
stat = os.stat(fp)
ftype = 'h5' if fn.endswith('.h5') else 'segy'
files.append({
'name': fn, 'size': stat.st_size,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'type': ftype
})
files.sort(key=lambda x: x['name'])
return jsonify({'files': files, 'count': len(files)})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/info')
def file_info(filename):
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
stat = os.stat(fp)
info = {
'name': filename,
'size': stat.st_size,
'size_mb': round(stat.st_size / 1048576, 2),
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
'type': 'h5' if filename.endswith('.h5') else 'segy',
}
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
datasets = []
def visitor(name, obj):
if isinstance(obj, h5py.Dataset):
datasets.append({
'path': name,
'shape': list(obj.shape),
'dtype': str(obj.dtype),
'samples': int(obj.shape[0]) if len(obj.shape) > 0 else 0
})
f.visititems(visitor)
info['datasets'] = datasets
info['num_datasets'] = len(datasets)
cal = [d for d in datasets if 'calibrated' in d['path']]
raw = [d for d in datasets if 'raw' in d['path']]
info['calibrated_channels'] = len(cal)
info['raw_channels'] = len(raw)
if cal:
info['samples_per_channel'] = cal[0]['samples']
# Add sample rate and duration for sidebar display
if 'metadata' in f:
sample_rate = f['metadata'].attrs.get('sample_rate_hz', None)
if sample_rate and cal:
total_samples = cal[0]['samples']
duration_sec = total_samples / sample_rate
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
info['duration_human'] = f"{hours}h{minutes:02d}m"
info['sample_rate_hz'] = sample_rate
info['num_channels'] = len(cal)
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp, headonly=True)
info['num_traces'] = len(st)
if len(st) > 0:
info['samples_per_trace'] = st[0].stats.npts
info['sample_rate'] = st[0].stats.sampling_rate
info['delta'] = st[0].stats.delta
except Exception as e:
info['segy_error'] = str(e)
return jsonify(info)
except Exception as e:
return jsonify({'error': str(e)}), 500
# ========== NEW: METADATA ENDPOINT ==========
@app.route('/api/file/<filename>/metadata')
def file_metadata(filename):
"""Return detailed metadata for a file."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
stat = os.stat(fp)
result = {
'filename': filename,
'file_size_bytes': stat.st_size,
'file_size_mb': round(stat.st_size / 1048576, 2),
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
}
if filename.endswith('.h5'):
metadata = get_h5_metadata(fp)
result.update(metadata)
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp, headonly=True)
result['num_traces'] = len(st)
if len(st) > 0:
result['sample_rate'] = st[0].stats.sampling_rate
result['samples_per_trace'] = st[0].stats.npts
result['start_timestamp'] = st[0].stats.starttime.isoformat()
result['end_timestamp'] = st[0].stats.endtime.isoformat()
duration_sec = (st[0].stats.endtime - st[0].stats.starttime)
hours = int(duration_sec // 3600)
minutes = int((duration_sec % 3600) // 60)
seconds = int(duration_sec % 60)
result['duration_human'] = f"{hours}h {minutes}m {seconds}s"
result['duration_sec'] = duration_sec
except Exception as e:
result['segy_error'] = str(e)
return jsonify(result)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== NEW: LOCATIONS ENDPOINT ==========
@app.route('/api/locations', methods=['GET'])
def get_locations():
"""Get all file locations."""
try:
locations = load_locations()
return jsonify(locations)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/locations', methods=['POST'])
def update_location():
"""Update location for a file."""
try:
data = request.json
filename = data.get('filename')
lat = data.get('lat')
lon = data.get('lon')
if not all([filename, lat is not None, lon is not None]):
return jsonify({'error': 'Missing filename, lat, or lon'}), 400
locations = load_locations()
locations[filename] = {
'lat': float(lat),
'lon': float(lon),
'updated': datetime.now().isoformat()
}
save_locations(locations)
return jsonify({'success': True, 'locations': locations})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== NEW: EXTRACT ENDPOINT ==========
@app.route('/api/file/<filename>/extract')
def extract_segment(filename):
"""Extract a time segment from a file and return as CSV or H5."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
# Parameters
start_sec = float(request.args.get('start_sec', 0))
duration_sec = float(request.args.get('duration_sec', 1200)) # Default 20 min
channel = request.args.get('channel', 'calibrated_data/channel_1')
format_type = request.args.get('format', 'csv') # csv or h5
if not filename.endswith('.h5'):
return jsonify({'error': 'Extract only supported for H5 files'}), 400
with h5py.File(fp, 'r') as f:
if channel not in f:
return jsonify({'error': f'Channel {channel} not found'}), 404
# Get metadata
sample_rate = f['metadata'].attrs.get('sample_rate_hz', 500)
total_samples = f[channel].shape[0]
# Calculate sample range
start_sample = int(start_sec * sample_rate)
end_sample = int((start_sec + duration_sec) * sample_rate)
# Clamp to valid range
start_sample = max(0, min(start_sample, total_samples - 1))
end_sample = max(start_sample + 1, min(end_sample, total_samples))
# Extract data
data = f[channel][start_sample:end_sample]
if format_type == 'csv':
# Generate CSV
output = io.StringIO()
output.write(f"# Channel: {channel}\n")
output.write(f"# Start: {start_sec}s, Duration: {duration_sec}s\n")
output.write(f"# Sample rate: {sample_rate} Hz\n")
output.write(f"# Samples: {len(data)}\n")
output.write("sample,time_sec,value\n")
for i, val in enumerate(data):
time_s = (start_sample + i) / sample_rate
output.write(f"{start_sample + i},{time_s:.6f},{val}\n")
output.seek(0)
return send_file(
io.BytesIO(output.getvalue().encode('utf-8')),
mimetype='text/csv',
as_attachment=True,
download_name=f'{filename.replace(".h5", "")}_extract_{int(start_sec)}_{int(duration_sec)}s.csv'
)
elif format_type == 'h5':
# Generate H5
output = io.BytesIO()
with h5py.File(output, 'w') as out_f:
# Copy metadata
if 'metadata' in f:
meta_group = out_f.create_group('metadata')
for k, v in f['metadata'].attrs.items():
meta_group.attrs[k] = v
meta_group.attrs['extract_start_sec'] = start_sec
meta_group.attrs['extract_duration_sec'] = duration_sec
meta_group.attrs['extract_start_sample'] = start_sample
meta_group.attrs['extract_end_sample'] = end_sample
# Create dataset
ds = out_f.create_dataset('data', data=data, compression='gzip')
ds.attrs['channel'] = channel
ds.attrs['sample_rate_hz'] = sample_rate
# Copy channel attributes
if channel in f:
for k, v in f[channel].attrs.items():
ds.attrs[k] = v
output.seek(0)
return send_file(
output,
mimetype='application/x-hdf5',
as_attachment=True,
download_name=f'{filename.replace(".h5", "")}_extract_{int(start_sec)}_{int(duration_sec)}s.h5'
)
else:
return jsonify({'error': 'Invalid format. Use csv or h5'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
# ========== EXISTING ENDPOINTS (kept for compatibility) ==========
@app.route('/api/file/<filename>/section')
def file_section(filename):
"""Return 2D section data as base64 float32 array."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
trace_start = int(request.args.get('trace_start', 0))
trace_count = int(request.args.get('trace_count', 200))
samples_per_trace = int(request.args.get('samples_per_trace', 1000))
channel = request.args.get('channel', 'calibrated_data/channel_1')
max_samples = int(request.args.get('max_samples', 500))
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
if channel not in f:
avail = []
f.visititems(lambda n, o: avail.append(n) if isinstance(o, h5py.Dataset) else None)
return jsonify({'error': f'Channel not found', 'available': avail}), 404
data = f[channel]
total_samples = data.shape[0]
total_traces = total_samples // samples_per_trace
trace_start = max(0, min(trace_start, max(0, total_traces - 1)))
trace_end = min(trace_start + trace_count, total_traces)
actual_count = trace_end - trace_start
if actual_count <= 0:
return jsonify({'error': 'No traces in range'}), 400
start_sample = trace_start * samples_per_trace
end_sample = trace_end * samples_per_trace
raw = data[start_sample:end_sample]
section = raw.reshape(actual_count, samples_per_trace).astype(np.float32)
if samples_per_trace > max_samples:
factor = samples_per_trace // max_samples
trimmed = section[:, :factor * max_samples]
section = trimmed.reshape(actual_count, max_samples, factor).mean(axis=2).astype(np.float32)
actual_spt = max_samples
else:
actual_spt = samples_per_trace
vmin = float(np.min(section))
vmax = float(np.max(section))
encoded = base64.b64encode(section.tobytes()).decode('ascii')
return jsonify({
'num_traces': int(actual_count),
'samples_per_trace': actual_spt,
'trace_start': trace_start,
'total_traces': total_traces,
'total_samples': int(total_samples),
'vmin': vmin, 'vmax': vmax,
'data_b64': encoded,
'dtype': 'float32'
})
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
st = obspy_read(fp)
total_traces = len(st)
trace_start = max(0, min(trace_start, total_traces - 1))
trace_end = min(trace_start + trace_count, total_traces)
actual_count = trace_end - trace_start
if actual_count <= 0:
return jsonify({'error': 'No traces'}), 400
spt = st[0].stats.npts
actual_spt = spt
section = np.zeros((actual_count, spt), dtype=np.float32)
for i in range(actual_count):
tr = st[trace_start + i]
n = min(spt, tr.stats.npts)
section[i, :n] = tr.data[:n].astype(np.float32)
if spt > max_samples:
factor = spt // max_samples
trimmed = section[:, :factor * max_samples]
section = trimmed.reshape(actual_count, max_samples, factor).mean(axis=2).astype(np.float32)
actual_spt = max_samples
vmin = float(np.min(section))
vmax = float(np.max(section))
encoded = base64.b64encode(section.tobytes()).decode('ascii')
return jsonify({
'num_traces': int(actual_count),
'samples_per_trace': actual_spt,
'trace_start': trace_start,
'total_traces': total_traces,
'total_samples': int(total_traces * spt),
'vmin': vmin, 'vmax': vmax,
'data_b64': encoded,
'dtype': 'float32'
})
except Exception as e:
return jsonify({'error': f'SEGY read error: {e}'}), 500
return jsonify({'error': 'Unsupported format'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/headers')
def file_headers(filename):
"""Return trace headers for SEGY or dataset attributes for H5."""
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
attrs = {}
for k, v in f.attrs.items():
try:
attrs[k] = v.item() if hasattr(v, 'item') else str(v)
except:
attrs[k] = str(v)
datasets = []
def visitor(name, obj):
if isinstance(obj, h5py.Dataset):
ds_info = {
'path': name,
'shape': list(obj.shape),
'dtype': str(obj.dtype),
'size': int(np.prod(obj.shape)),
}
for ak, av in obj.attrs.items():
try:
ds_info[f'attr:{ak}'] = av.item() if hasattr(av, 'item') else str(av)
except:
ds_info[f'attr:{ak}'] = str(av)
datasets.append(ds_info)
f.visititems(visitor)
groups = []
def gvisitor(name, obj):
if isinstance(obj, h5py.Group):
g_info = {'path': name}
for ak, av in obj.attrs.items():
try:
g_info[f'attr:{ak}'] = av.item() if hasattr(av, 'item') else str(av)
except:
g_info[f'attr:{ak}'] = str(av)
if len(g_info) > 1:
groups.append(g_info)
f.visititems(gvisitor)
return jsonify({
'type': 'h5',
'file_attrs': attrs,
'datasets': datasets,
'groups': groups
})
elif filename.endswith(('.segy', '.sgy')):
try:
from obspy import read as obspy_read
from obspy.io.segy.segy import _read_segy
segy = _read_segy(fp)
bfh = {}
if hasattr(segy, 'binary_file_header'):
h = segy.binary_file_header
for attr in ['job_identification_number', 'line_number', 'reel_number',
'number_of_data_traces_per_ensemble', 'number_of_auxiliary_traces_per_ensemble',
'sample_interval_in_microseconds', 'number_of_samples_per_data_trace',
'data_sample_format_code']:
if hasattr(h, attr):
bfh[attr] = getattr(h, attr)
trace_headers = []
max_traces = min(50, len(segy.traces))
for i in range(max_traces):
th = segy.traces[i].header
trace_headers.append({
'trace': i,
'inline': getattr(th, 'trace_sequence_number_within_line', None),
'crossline': getattr(th, 'trace_sequence_number_within_segy_file', None),
'field_record': getattr(th, 'original_field_record_number', None),
'trace_number': getattr(th, 'trace_number_within_the_original_field_record', None),
'cdp': getattr(th, 'ensemble_number', None),
'trace_in_ensemble': getattr(th, 'trace_number_within_the_ensemble', None),
'offset': getattr(th, 'distance_from_center_of_the_source_point_to_the_center_of_the_receiver_group', None),
'source_x': getattr(th, 'source_coordinate_x', None),
'source_y': getattr(th, 'source_coordinate_y', None),
'group_x': getattr(th, 'group_coordinate_x', None),
'group_y': getattr(th, 'group_coordinate_y', None),
'num_samples': getattr(th, 'number_of_samples_in_this_trace', None),
'sample_interval': getattr(th, 'sample_interval_in_ms_for_this_trace', None),
})
return jsonify({
'type': 'segy',
'binary_header': bfh,
'trace_headers': trace_headers,
'total_traces': len(segy.traces)
})
except Exception as e:
return jsonify({'error': f'SEGY header read error: {e}'}), 500
return jsonify({'error': 'Unsupported'}), 400
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/file/<filename>/waveform')
def file_waveform(filename):
try:
fp = os.path.join(DATA_DIR, filename)
if not os.path.exists(fp):
return jsonify({'error': 'Not found'}), 404
max_pts = int(request.args.get('points', 5000))
channel = request.args.get('channel', 'calibrated_data/channel_1')
if filename.endswith('.h5'):
with h5py.File(fp, 'r') as f:
if channel not in f:
return jsonify({'error': 'Channel not found'}), 404
data = f[channel][:]
n = len(data)
if n <= max_pts:
values = data.tolist()
indices = list(range(n))
else:
step = n // max_pts
indices = list(range(0, n, step))[:max_pts]
values = [float(data[i]) for i in indices]
return jsonify({
'name': filename, 'channel': channel,
'total_samples': n,
'displayed_points': len(values),
'min': float(np.min(data)), 'max': float(np.max(data)),
'mean': float(np.mean(data)), 'std': float(np.std(data)),
'x': indices, 'y': values
})
return jsonify({'error': 'Waveform only for H5'}), 400
except Exception as e:
return jsonify({'error': str(e)}), 500
# ========== GEOSUP ENDPOINTS ==========
# UTM Zone 31N to WGS84 transformer
utm_to_wgs84 = Transformer.from_crs("EPSG:32631", "EPSG:4326", always_xy=True)
GEOSUP_DIR = os.path.join(app.static_folder, '') # Static folder
def parse_sps_line(line):
"""Parse SPS S01/R01 format line. Fixed-width format."""
if len(line) < 65:
return None
try:
record_type = line[0].strip() # S or R
line_num = line[1:11].strip()
point = line[11:21].strip()
index = line[21:23].strip()
# Easting and Northing are in columns ~26-52
coord_part = line[46:66].strip()
# Parse XXXXXX.XNNNNNNN.NN format
match = re.match(r'(\d{5,6}\.\d{1,2})(\d{7}\.\d{1,2})', coord_part)
if match:
easting = float(match.group(1))
northing = float(match.group(2))
else:
parts = coord_part.split('.')
if len(parts) >= 2:
easting_int = parts[0]
rest = '.'.join(parts[1:])
if len(rest) > 9:
easting = float(easting_int + '.' + rest[:2])
northing = float(rest[2:])
else:
return None
else:
return None
depth = line[66:72].strip() if len(line) > 52 else '0'
depth = float(depth) if depth else 0
return {
'type': record_type,
'line': line_num,
'point': point,
'index': index,
'easting': easting,
'northing': northing,
'depth': depth
}
except Exception:
return None
def parse_deployment_csv(filepath):
"""Parse deployment CSV file (SETE-0965P1002.csv format)."""
records = []
try:
with open(filepath, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 8:
try:
record = {
'line': parts[0],
'point': parts[1],
'index': parts[2],
'name': parts[3],
'easting': float(parts[5]),
'northing': float(parts[6]),
'depth': float(parts[7]),
}
if len(parts) >= 16:
record['year'] = int(parts[10])
record['day'] = int(parts[11])
record['hour'] = int(parts[12])
record['minute'] = int(parts[13])
record['second'] = int(parts[14])
records.append(record)
except (ValueError, IndexError):
continue
except Exception:
pass
return records
@app.route('/api/geosup/positions')
def geosup_positions():
"""Return GeoJSON with source, receiver, and deployment positions."""
try:
features = []
# Parse sources (S01)
s01_path = os.path.join(GEOSUP_DIR, 'SeteSxPreplots.s01')
if os.path.exists(s01_path):
with open(s01_path, 'r') as f:
for line in f:
record = parse_sps_line(line)
if record and record['type'] == 'S':
lon, lat = utm_to_wgs84.transform(record['easting'], record['northing'])
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': {
'category': 'source',
'line': record['line'],
'point': record['point'],
'easting': record['easting'],
'northing': record['northing'],
'depth': record['depth']
}
})
# Parse receivers (R01)
r01_path = os.path.join(GEOSUP_DIR, 'SeteRxPreplots.r01')
if os.path.exists(r01_path):
with open(r01_path, 'r') as f:
for line in f:
record = parse_sps_line(line)
if record and record['type'] == 'R':
lon, lat = utm_to_wgs84.transform(record['easting'], record['northing'])
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': {
'category': 'receiver',
'line': record['line'],
'point': record['point'],
'easting': record['easting'],
'northing': record['northing'],
'depth': record['depth']
}
})
# Parse deployments (CSV)
csv_path = os.path.join(GEOSUP_DIR, 'SETE-0965P1002.csv')
if os.path.exists(csv_path):
deployments = parse_deployment_csv(csv_path)
for d in deployments:
lon, lat = utm_to_wgs84.transform(d['easting'], d['northing'])
props = {
'category': 'deployment',
'line': d['line'],
'point': d['point'],
'name': d['name'],
'easting': d['easting'],
'northing': d['northing'],
'depth': d['depth']
}
if 'year' in d:
props['timestamp'] = f"{d['year']}-{d['day']:03d} {d['hour']:02d}:{d['minute']:02d}:{d['second']:02d}"
features.append({
'type': 'Feature',
'geometry': {'type': 'Point', 'coordinates': [lon, lat]},
'properties': props
})
sources = len([f for f in features if f['properties']['category'] == 'source'])
receivers = len([f for f in features if f['properties']['category'] == 'receiver'])
deployments = len([f for f in features if f['properties']['category'] == 'deployment'])
return jsonify({
'type': 'FeatureCollection',
'features': features,
'counts': {'sources': sources, 'receivers': receivers, 'deployments': deployments, 'total': len(features)}
})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/documents')
def geosup_documents():
"""List all geosup documents in static folder."""
try:
documents = []
doc_extensions = ('.pdf', '.docx', '.xlsx', '.doc', '.xls')
for fn in os.listdir(GEOSUP_DIR):
if fn.lower().endswith(doc_extensions):
fp = os.path.join(GEOSUP_DIR, fn)
stat = os.stat(fp)
ext = os.path.splitext(fn)[1].lower()
doc_type = {'.pdf': 'PDF', '.docx': 'Word', '.doc': 'Word', '.xlsx': 'Excel', '.xls': 'Excel'}.get(ext, 'Document')
icon = {'.pdf': '📕', '.docx': '📘', '.doc': '📘', '.xlsx': '📗', '.xls': '📗'}.get(ext, '📄')
is_gundalf = 'GUNDALF' in fn.upper()
is_params = 'Acquisition_Parameters' in fn or 'SpiceRack' in fn
documents.append({
'name': fn,
'size': stat.st_size,
'size_human': f"{stat.st_size / 1024:.1f} KB" if stat.st_size < 1048576 else f"{stat.st_size / 1048576:.1f} MB",
'type': doc_type,
'icon': icon,
'is_gundalf': is_gundalf,
'is_params': is_params,
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat()
})
documents.sort(key=lambda d: (not d['is_gundalf'], not d['is_params'], d['name']))
return jsonify({'documents': documents, 'count': len(documents)})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/documents/<path:filename>')
def geosup_download(filename):
"""Download a geosup document."""
try:
safe_name = os.path.basename(filename)
filepath = os.path.join(GEOSUP_DIR, safe_name)
if not os.path.exists(filepath):
return jsonify({'error': 'File not found'}), 404
mime_type, _ = mimetypes.guess_type(filepath)
return send_file(filepath, mimetype=mime_type or 'application/octet-stream', as_attachment=True, download_name=safe_name)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/geosup/acquisition-params')
def geosup_acquisition_params():
"""Extract key parameters from the acquisition spreadsheet."""
try:
xlsx_path = os.path.join(GEOSUP_DIR, 'SBGS_Sete_SpiceRack_Acquisition_Parameters_Spreadsheet.xlsx')
if not os.path.exists(xlsx_path):
return jsonify({'error': 'Acquisition parameters file not found'}), 404
from openpyxl import load_workbook
wb = load_workbook(xlsx_path, data_only=True)
params = {'sheets': [], 'summary': {}}
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
sheet_data = {'name': sheet_name, 'rows': []}
row_count = 0
for row in ws.iter_rows(max_row=30, values_only=True):
if any(cell is not None for cell in row):
sheet_data['rows'].append([str(cell) if cell is not None else '' for cell in row[:10]])
row_count += 1
if row_count >= 20:
break
params['sheets'].append(sheet_data)
wb.close()
return jsonify(params)
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
@app.route('/api/pipeline-status')
def pipeline_status():
"""Return pipeline statistics and disk usage."""
try:
raw_dir = '/mnt/usb/Recordings'
output_dir = '/mnt/usb/H5-Output'
# Count files
raw_count = 0
if os.path.exists(raw_dir):
raw_count = len([n for n in os.listdir(raw_dir) if n.lower().endswith(('.raw', '.manta'))])
segy_count = 0
h5_count = 0
if os.path.exists(output_dir):
files = os.listdir(output_dir)
segy_count = len([n for n in files if n.lower().endswith(('.segy', '.sgy'))])
h5_count = len([n for n in files if n.lower().endswith('.h5')])
# Disk usage
total, used, free = shutil.disk_usage(output_dir)
# Conversion progress
progress = None
if os.path.exists('/tmp/segy2h5_progress.json'):
try:
with open('/tmp/segy2h5_progress.json', 'r') as f:
progress = json.load(f)
except:
pass
return jsonify({
'counts': {
'raw': raw_count,
'segy': segy_count,
'h5': h5_count,
'total_expected': 345 # Hardcoded target from context
},
'disk': {
'total_gb': round(total / (1024**3), 2),
'used_gb': round(used / (1024**3), 2),
'free_gb': round(free / (1024**3), 2),
'percent': round((used / total) * 100, 1)
},
'progress': progress
})
except Exception as e:
traceback.print_exc()
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3001, debug=False)
@app.route('/test-map')
def test_map():
return send_from_directory('static', 'test-map.html')

1627
app.py.v3 Normal file

File diff suppressed because it is too large Load Diff

250
backend_src/index.ts Normal file
View File

@@ -0,0 +1,250 @@
import express from 'express';
import cors from 'cors';
import { readFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
import { join } from 'path';
import { spawn } from 'child_process';
import { Pool } from 'pg';
const app = express();
const PORT = 3001;
app.use(cors());
app.use(express.json());
const pool = new Pool({
connectionString: process.env.DATABASE_URL || 'postgresql://postgres:seismic_pass@db:5432/seismic_data'
});
const INDEX_PATH = '/mnt/kingston/seismic_webapp/data/index.json';
const EXPORT_DIR = '/mnt/kingston/seismic_webapp/exports';
if (!existsSync(EXPORT_DIR)) mkdirSync(EXPORT_DIR, { recursive: true });
// GET /api/migration-status - Renvoie l'état de la migration en cours
app.get('/api/migration-status', async (req, res) => {
try {
const result = await pool.query('SELECT total_files, processed_files, current_file FROM migration_status WHERE id = 1');
if (result.rows.length > 0) {
res.json(result.rows[0]);
} else {
res.status(404).json({ error: 'Status not found' });
}
} catch (err) {
res.status(500).json({ error: 'DB Error' });
}
});
app.get('/api/nodes', (req, res) => {
try {
const index = JSON.parse(readFileSync(INDEX_PATH, 'utf-8'));
const nodes = Object.values(index?.nodes || {}).map((n: any) => ({
id: n.id, position: n.position, hasDates: n.files && n.files.length > 0
}));
res.json({ nodes });
} catch (e) { res.status(500).json({ error: 'Index read error' }); }
});
app.get('/api/dates', (req, res) => {
try {
const index = JSON.parse(readFileSync(INDEX_PATH, 'utf-8'));
res.json({ dates: index?.dates || [] });
} catch (e) { res.json({ dates: [] }); }
});
app.get('/api/rms-timeline', (req, res) => {
const { date, channel } = req.query;
const cachePath = `/mnt/kingston/seismic_webapp/data/rms_cache/rms_${date}_${channel}.json`;
if (existsSync(cachePath)) res.json(JSON.parse(readFileSync(cachePath, 'utf-8')));
else res.json({ nodes: {} });
});
app.get('/api/global-history', (req, res) => {
const { channel } = req.query;
const dir = '/mnt/kingston/seismic_webapp/data/rms_cache';
const nodes: Record<string, any[]> = {};
try {
if (existsSync(dir)) {
readdirSync(dir).filter((f:string) => f.endsWith(`_${channel}.json`)).forEach((f:string) => {
const data = JSON.parse(readFileSync(`${dir}/${f}`, 'utf-8'));
Object.entries(data.nodes).forEach(([id, pts]: [string, any]) => {
if (!nodes[id]) nodes[id] = [];
nodes[id].push(...pts);
});
});
}
} catch (e) {}
res.json({ nodes });
});
app.get('/api/data', async (req, res) => {
const { node, start, channel } = req.query;
const ts = parseInt(start as string);
const startTime = new Date(ts * 1000);
const endTime = new Date((ts + 10) * 1000);
try {
const dbRes = await pool.query('SELECT value FROM adc_samples WHERE node_id = $1 AND channel = $2 AND time >= $3 AND time < $4 ORDER BY time ASC', [node, channel, startTime, endTime]);
if (dbRes.rows.length > 0) return res.json({ samples: dbRes.rows.map(r => r.value), source: 'db' });
const index = JSON.parse(readFileSync(INDEX_PATH, 'utf-8'));
const targetFile = index?.nodes[node as string]?.files?.find((f: any) => ts >= f.start && ts <= f.end && (f.channel === channel || f.path.includes(`_${channel}_`)));
if (!targetFile) return res.status(404).json({ error: 'File not found' });
const fixPath = (p: string) => p.replace(/\\/g, '/').replace('F:/', '/mnt/kingston/').replace('E:/', '/mnt/data_sdb1/');
const proc = spawn('python3', ['/app/scripts/extract_hdf5_window.py', '--file', fixPath(targetFile.path), '--channel', (channel as string).replace('ch',''), '--start', ts.toString(), '--duration', '10']);
let stdout = '';
proc.stdout.on('data', (d) => stdout += d.toString());
proc.on('close', () => {
try { res.json(JSON.parse(stdout)); } catch (e) { res.status(500).json({ error: 'Python error' }); }
});
} catch (err) { res.status(500).json({ error: 'Server error' }); }
});
app.post('/api/chat', (req, res) => {
const { message } = req.body;
const prompt = `Assistant Seismic. User: ${message}`;
const proc = spawn('/home/floppyrj45/.local/bin/cursor-agent', ['--print', '--force', prompt]);
let output = '';
proc.stdout.on('data', (d) => output += d.toString());
proc.on('close', () => res.json({ response: output }));
});
app.get('/api/export', (req, res) => {
const { node, date, channel, start } = req.query;
try {
const index = JSON.parse(readFileSync(INDEX_PATH, 'utf-8'));
const targetFile = index?.nodes[node as string]?.files?.find((f:any) => f.path.includes(`_${channel}_`));
if (!targetFile) return res.status(404).send('Not found');
const fixPath = (p: string) => p.replace(/\\/g, '/').replace('F:/', '/mnt/kingston/').replace('E:/', '/mnt/data_sdb1/');
const proc = spawn('python3', ['/app/scripts/export_csv.py', '--file', fixPath(targetFile.path), '--start', start as string, '--duration', '3600', '--output', `/tmp/export_${node}.csv`]);
proc.on('close', () => res.download(`/tmp/export_${node}.csv`));
} catch (e) { res.status(500).send('Export error'); }
});
app.listen(PORT, () => console.log(`API port ${PORT}`));
// H5 Coverage Endpoints
import sqlite3 from 'sqlite3';
const h5db = new sqlite3.Database('/app/h5_data.db', sqlite3.OPEN_READONLY, (err: any) => {
if (err) console.error('H5 DB not available:', err.message);
});
app.get('/api/h5/coverage', (req, res) => {
h5db.all(`
SELECT
COUNT(*) as total_positions,
SUM(CASE WHEN has_data = 1 THEN 1 ELSE 0 END) as with_data,
SUM(CASE WHEN has_aux = 1 THEN 1 ELSE 0 END) as with_aux,
SUM(sample_count) as total_files
FROM positions
`, (err: any, rows: any) => {
if (err) return res.status(500).json({ error: err.message });
const stats = rows[0];
const coverage_pct = ((stats.with_data / stats.total_positions) * 100).toFixed(1);
res.json({
total_positions: stats.total_positions,
with_data: stats.with_data,
with_aux: stats.with_aux,
total_files: stats.total_files,
coverage_pct: parseFloat(coverage_pct),
missing: stats.total_positions - stats.with_data
});
});
});
app.get('/api/h5/gaps', (req, res) => {
h5db.all('SELECT node_code FROM positions WHERE has_data = 0 ORDER BY node_code', (err: any, rows: any) => {
if (err) return res.status(500).json({ error: err.message });
const missing = rows.map((r: any) => r.node_code);
const gaps = [];
let gapStart = null;
for (let i = 0; i < missing.length; i++) {
if (i === 0 || missing[i] !== missing[i-1] + 1) {
if (gapStart !== null) {
gaps.push({ start: gapStart, end: missing[i-1], length: missing[i-1] - gapStart + 1 });
}
gapStart = missing[i];
}
}
if (gapStart !== null) {
gaps.push({ start: gapStart, end: missing[missing.length - 1], length: missing[missing.length - 1] - gapStart + 1 });
}
res.json({ gaps, total_missing: missing.length });
});
});
// GET /api/conversion-status - Proxy vers la VM de conversion
app.get('/api/conversion-status', async (req, res) => {
try {
const response = await fetch('http://192.168.0.81:8000/conversion-progress.json');
const data = await response.json();
res.json(data);
} catch (err) {
res.status(503).json({
error: 'VM conversion inaccessible',
total: 345,
processed: 0,
errors: 0,
percent: 0,
eta_minutes: 0
});
}
});
// ============ H5 2026 Format Endpoints ============
app.get('/api/h5/files', (req, res) => {
const h5Dir = '/home/floppyrj45/docker/seismic-nodes-viewer/data/h5';
try {
const files = readdirSync(h5Dir)
.filter(f => f.endsWith('.h5'))
.map(filename => {
const match = filename.match(/rsn(\d+)/);
const nodeId = match ? match[1] : 'unknown';
const matchDate = filename.match(/_(\d{6})_/);
const date = matchDate ? matchDate[1] : '';
return { filename, nodeId, date, path: `${h5Dir}/${filename}` };
});
res.json({ files, count: files.length });
} catch (e) {
res.status(500).json({ error: 'Cannot list H5 files' });
}
});
app.get('/api/h5/data', (req, res) => {
const { file, channel, start, duration } = req.query;
const filePath = `/home/floppyrj45/docker/seismic-nodes-viewer/data/h5/${file}`;
const proc = spawn('python3', [
'/home/floppyrj45/docker/seismic-nodes-viewer/scripts/extract_h5_calibrated.py',
'--file', filePath,
'--channel', channel as string,
'--start', (start || '0') as string,
'--duration', (duration || '10') as string
]);
let stdout = '';
proc.stdout.on('data', d => stdout += d.toString());
proc.on('close', () => {
try {
res.json(JSON.parse(stdout));
} catch (e) {
res.status(500).json({ error: 'Python script error', raw: stdout });
}
});
});
// Docs endpoints
app.get('/api/docs/manifest', (req, res) => {
res.sendFile('/data/docs/campaign_manifest.json');
});
app.get('/api/docs/:filename', (req, res) => {
const file = req.params.filename;
res.sendFile(`/data/docs/${file}`);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -0,0 +1,61 @@
965,4961,1,A1,2,558568.9,4797608.3,34.1,3,0,2020,250,9,30,3,672023
965,4963,1,A1,2,558610.5,4797635.4,34.1,2.7,0,2020,250,9,30,25,460017
965,4965,1,A1,2,558653.3,4797661.3,34.1,2.9,0,2020,250,9,30,47,260023
965,4967,1,A1,2,558696.1,4797687.2,34.1,2.8,0,2020,250,9,31,9,220018
965,4969,1,A1,2,558738,4797714.6,34.1,2.9,0,2020,250,9,31,31,388019
965,4971,1,A1,2,558775,4797748.8,34.3,2.9,0,2020,250,9,31,54,128017
965,4973,1,A1,2,558812.1,4797783.9,34.1,2.8,0,2020,250,9,32,16,896020
965,4975,1,A1,2,558854,4797810.7,34.1,2.8,0,2020,250,9,32,38,384020
965,4977,1,A1,2,558897.7,4797835.2,34.1,2.8,0,2020,250,9,32,59,984020
965,4979,1,A1,2,558938.4,4797864.3,34.1,2.8,0,2020,250,9,33,21,892020
965,4981,1,A1,2,558979.4,4797892.9,34.1,2.7,0,2020,250,9,33,43,764021
965,4983,1,A1,2,559018.8,4797923.9,34.1,2.8,0,2020,250,9,34,5,740021
965,4985,1,A1,2,559059.7,4797952.7,34.1,2.9,0,2020,250,9,34,27,604022
965,4987,1,A1,2,559100.5,4797981.6,34.2,2.9,0,2020,250,9,34,49,332021
965,4989,1,A1,2,559142.6,4798008.5,34.1,2.9,0,2020,250,9,35,10,992021
965,4991,1,A1,2,559183.4,4798037.5,34.1,2.9,0,2020,250,9,35,32,836021
965,4993,1,A1,2,559225.3,4798064.8,34.1,2.8,0,2020,250,9,35,54,632022
965,4995,1,A1,2,559265.9,4798093.9,34.1,2.9,0,2020,250,9,36,16,444021
965,4997,1,A1,2,559306.2,4798123.6,33.9,2.8,0,2020,250,9,36,38,336022
965,4999,1,A1,2,559347.7,4798151.6,34.2,2.8,0,2020,250,9,37,0,136022
965,5001,1,A1,2,559388.7,4798180.2,34.2,2.9,0,2020,250,9,37,21,996023
965,5003,1,A1,2,559428.5,4798210.6,34.1,2.8,0,2020,250,9,37,43,832017
965,5005,1,A1,2,559470,4798238.3,33.5,2.8,0,2020,250,9,38,5,452022
965,5007,1,A1,2,559510.7,4798267.4,34.1,2.9,0,2020,250,9,38,27,292022
965,5009,1,A1,2,559550.9,4798297.2,34.2,2.8,0,2020,250,9,38,49,204023
965,5011,1,A1,2,559591.8,4798326,34.1,2.8,0,2020,250,9,39,11,8017
965,5013,1,A1,2,559633.5,4798353.7,34.1,2.9,0,2020,250,9,39,32,744044
965,5015,1,A1,2,559675.1,4798381.3,34.2,2.8,0,2020,250,9,39,54,440023
965,5017,1,A1,2,559715.2,4798411.2,34.1,2.8,0,2020,250,9,40,16,316022
965,5019,1,A1,2,559756.7,4798439.1,34.2,2.8,0,2020,250,9,40,38,112017
965,5021,1,A1,2,559797.4,4798468,34.2,2.9,0,2020,250,9,41,0,92018
965,5023,1,A1,2,559838.4,4798496.9,34.1,2.9,0,2020,250,9,41,22,104018
965,5025,1,A1,2,559880,4798524.5,34.2,2.9,0,2020,250,9,41,43,996020
965,5027,1,A1,2,559920.9,4798553.4,34.1,2.9,0,2020,250,9,42,5,944019
965,5029,1,A1,2,559962.2,4798581.5,34.2,2.8,0,2020,250,9,42,27,864021
965,5031,1,A1,2,560004,4798609,34.2,2.8,0,2020,250,9,42,49,656021
965,5033,1,A1,2,560045.7,4798636.5,34.2,2.8,0,2020,250,9,43,11,452020
965,5035,1,A1,2,560085.9,4798666.5,34.1,2.8,0,2020,250,9,43,33,512021
965,5037,1,A1,2,560127,4798694.9,34.1,2.9,0,2020,250,9,43,55,440022
965,5039,1,A1,2,560168.3,4798722.9,34.3,2.8,0,2020,250,9,44,17,424017
965,5041,1,A1,2,560208.3,4798753.1,34.1,2.8,0,2020,250,9,44,39,604018
965,5043,1,A1,2,560250.1,4798781.2,34.1,2.9,0,2020,250,9,45,0,944018
965,5045,1,A1,2,560290.2,4798811,34.2,2.8,0,2020,250,9,45,21,36021
965,5047,1,A1,2,560329.6,4798841.2,34.1,2.7,0,2020,250,9,45,40,728019
965,5049,1,A1,2,560370.5,4798869.9,34.1,2.7,0,2020,250,9,46,0,968019
965,5051,1,A1,2,560410.9,4798899.4,34.1,2.7,0,2020,250,9,46,21,420019
965,5053,1,A1,2,560451.2,4798929.3,34.1,2.7,0,2020,250,9,46,41,956020
965,5055,1,A1,2,560493.1,4798956.6,34.3,2.7,0,2020,250,9,47,2,200026
965,5057,1,A1,2,560533.9,4798985.4,34.1,2.7,0,2020,250,9,47,22,536019
965,5059,1,A1,2,560574.3,4799014.8,34.4,2.7,0,2020,250,9,47,43,108020
965,5061,1,A1,2,560616,4799042.7,34.4,2.7,0,2020,250,9,48,3,564021
965,5063,1,A1,2,560657.3,4799070.5,34.2,2.7,0,2020,250,9,48,23,944021
965,5065,1,A1,2,560697.6,4799100.3,34.4,2.7,0,2020,250,9,48,44,560022
965,5067,1,A1,2,560739.2,4799128.1,34.2,2.7,0,2020,250,9,49,5,88023
965,5069,1,A1,2,560779.5,4799157.7,34.4,2.7,0,2020,250,9,49,25,692018
965,5071,1,A1,2,560820.6,4799186.4,34.4,2.7,0,2020,250,9,49,46,120024
965,5073,1,A1,2,560862.5,4799213.6,34.4,2.7,0,2020,250,9,50,6,68022
965,5075,1,A1,2,560903,4799242.8,34.4,2.7,0,2020,250,9,50,26,176021
965,5077,1,A1,2,560944.5,4799270.8,34.4,2.7,0,2020,250,9,50,46,204019
965,5079,1,A1,2,560986.2,4799298.2,34.4,2.7,0,2020,250,9,51,6,152018
965,5081,1,A1,2,561026.4,4799328.2,34.4,2.7,0,2020,250,9,51,26,300023
1 965 4961 1 A1 2 558568.9 4797608.3 34.1 3 0 2020 250 9 30 3 672023
2 965 4963 1 A1 2 558610.5 4797635.4 34.1 2.7 0 2020 250 9 30 25 460017
3 965 4965 1 A1 2 558653.3 4797661.3 34.1 2.9 0 2020 250 9 30 47 260023
4 965 4967 1 A1 2 558696.1 4797687.2 34.1 2.8 0 2020 250 9 31 9 220018
5 965 4969 1 A1 2 558738 4797714.6 34.1 2.9 0 2020 250 9 31 31 388019
6 965 4971 1 A1 2 558775 4797748.8 34.3 2.9 0 2020 250 9 31 54 128017
7 965 4973 1 A1 2 558812.1 4797783.9 34.1 2.8 0 2020 250 9 32 16 896020
8 965 4975 1 A1 2 558854 4797810.7 34.1 2.8 0 2020 250 9 32 38 384020
9 965 4977 1 A1 2 558897.7 4797835.2 34.1 2.8 0 2020 250 9 32 59 984020
10 965 4979 1 A1 2 558938.4 4797864.3 34.1 2.8 0 2020 250 9 33 21 892020
11 965 4981 1 A1 2 558979.4 4797892.9 34.1 2.7 0 2020 250 9 33 43 764021
12 965 4983 1 A1 2 559018.8 4797923.9 34.1 2.8 0 2020 250 9 34 5 740021
13 965 4985 1 A1 2 559059.7 4797952.7 34.1 2.9 0 2020 250 9 34 27 604022
14 965 4987 1 A1 2 559100.5 4797981.6 34.2 2.9 0 2020 250 9 34 49 332021
15 965 4989 1 A1 2 559142.6 4798008.5 34.1 2.9 0 2020 250 9 35 10 992021
16 965 4991 1 A1 2 559183.4 4798037.5 34.1 2.9 0 2020 250 9 35 32 836021
17 965 4993 1 A1 2 559225.3 4798064.8 34.1 2.8 0 2020 250 9 35 54 632022
18 965 4995 1 A1 2 559265.9 4798093.9 34.1 2.9 0 2020 250 9 36 16 444021
19 965 4997 1 A1 2 559306.2 4798123.6 33.9 2.8 0 2020 250 9 36 38 336022
20 965 4999 1 A1 2 559347.7 4798151.6 34.2 2.8 0 2020 250 9 37 0 136022
21 965 5001 1 A1 2 559388.7 4798180.2 34.2 2.9 0 2020 250 9 37 21 996023
22 965 5003 1 A1 2 559428.5 4798210.6 34.1 2.8 0 2020 250 9 37 43 832017
23 965 5005 1 A1 2 559470 4798238.3 33.5 2.8 0 2020 250 9 38 5 452022
24 965 5007 1 A1 2 559510.7 4798267.4 34.1 2.9 0 2020 250 9 38 27 292022
25 965 5009 1 A1 2 559550.9 4798297.2 34.2 2.8 0 2020 250 9 38 49 204023
26 965 5011 1 A1 2 559591.8 4798326 34.1 2.8 0 2020 250 9 39 11 8017
27 965 5013 1 A1 2 559633.5 4798353.7 34.1 2.9 0 2020 250 9 39 32 744044
28 965 5015 1 A1 2 559675.1 4798381.3 34.2 2.8 0 2020 250 9 39 54 440023
29 965 5017 1 A1 2 559715.2 4798411.2 34.1 2.8 0 2020 250 9 40 16 316022
30 965 5019 1 A1 2 559756.7 4798439.1 34.2 2.8 0 2020 250 9 40 38 112017
31 965 5021 1 A1 2 559797.4 4798468 34.2 2.9 0 2020 250 9 41 0 92018
32 965 5023 1 A1 2 559838.4 4798496.9 34.1 2.9 0 2020 250 9 41 22 104018
33 965 5025 1 A1 2 559880 4798524.5 34.2 2.9 0 2020 250 9 41 43 996020
34 965 5027 1 A1 2 559920.9 4798553.4 34.1 2.9 0 2020 250 9 42 5 944019
35 965 5029 1 A1 2 559962.2 4798581.5 34.2 2.8 0 2020 250 9 42 27 864021
36 965 5031 1 A1 2 560004 4798609 34.2 2.8 0 2020 250 9 42 49 656021
37 965 5033 1 A1 2 560045.7 4798636.5 34.2 2.8 0 2020 250 9 43 11 452020
38 965 5035 1 A1 2 560085.9 4798666.5 34.1 2.8 0 2020 250 9 43 33 512021
39 965 5037 1 A1 2 560127 4798694.9 34.1 2.9 0 2020 250 9 43 55 440022
40 965 5039 1 A1 2 560168.3 4798722.9 34.3 2.8 0 2020 250 9 44 17 424017
41 965 5041 1 A1 2 560208.3 4798753.1 34.1 2.8 0 2020 250 9 44 39 604018
42 965 5043 1 A1 2 560250.1 4798781.2 34.1 2.9 0 2020 250 9 45 0 944018
43 965 5045 1 A1 2 560290.2 4798811 34.2 2.8 0 2020 250 9 45 21 36021
44 965 5047 1 A1 2 560329.6 4798841.2 34.1 2.7 0 2020 250 9 45 40 728019
45 965 5049 1 A1 2 560370.5 4798869.9 34.1 2.7 0 2020 250 9 46 0 968019
46 965 5051 1 A1 2 560410.9 4798899.4 34.1 2.7 0 2020 250 9 46 21 420019
47 965 5053 1 A1 2 560451.2 4798929.3 34.1 2.7 0 2020 250 9 46 41 956020
48 965 5055 1 A1 2 560493.1 4798956.6 34.3 2.7 0 2020 250 9 47 2 200026
49 965 5057 1 A1 2 560533.9 4798985.4 34.1 2.7 0 2020 250 9 47 22 536019
50 965 5059 1 A1 2 560574.3 4799014.8 34.4 2.7 0 2020 250 9 47 43 108020
51 965 5061 1 A1 2 560616 4799042.7 34.4 2.7 0 2020 250 9 48 3 564021
52 965 5063 1 A1 2 560657.3 4799070.5 34.2 2.7 0 2020 250 9 48 23 944021
53 965 5065 1 A1 2 560697.6 4799100.3 34.4 2.7 0 2020 250 9 48 44 560022
54 965 5067 1 A1 2 560739.2 4799128.1 34.2 2.7 0 2020 250 9 49 5 88023
55 965 5069 1 A1 2 560779.5 4799157.7 34.4 2.7 0 2020 250 9 49 25 692018
56 965 5071 1 A1 2 560820.6 4799186.4 34.4 2.7 0 2020 250 9 49 46 120024
57 965 5073 1 A1 2 560862.5 4799213.6 34.4 2.7 0 2020 250 9 50 6 68022
58 965 5075 1 A1 2 560903 4799242.8 34.4 2.7 0 2020 250 9 50 26 176021
59 965 5077 1 A1 2 560944.5 4799270.8 34.4 2.7 0 2020 250 9 50 46 204019
60 965 5079 1 A1 2 560986.2 4799298.2 34.4 2.7 0 2020 250 9 51 6 152018
61 965 5081 1 A1 2 561026.4 4799328.2 34.4 2.7 0 2020 250 9 51 26 300023

View File

@@ -0,0 +1,200 @@
R 1000 5000 1 559867.204797453.00 0.00
R 1000 5004 1 559949.104797510.40 0.00
R 1000 5008 1 560031.104797567.80 0.00
R 1000 5012 1 560113.004797625.10 0.00
R 1000 5016 1 560194.904797682.50 0.00
R 1000 5020 1 560276.804797739.80 0.00
R 1000 5024 1 560358.704797797.20 0.00
R 1000 5028 1 560440.604797854.60 0.00
R 1000 5032 1 560522.504797911.90 0.00
R 1000 5036 1 560604.504797969.30 0.00
R 1000 5040 1 560686.404798026.60 0.00
R 1000 5044 1 560768.304798084.00 0.00
R 1000 5048 1 560850.204798141.30 0.00
R 1000 5052 1 560932.104798198.70 0.00
R 1000 5056 1 561014.004798256.10 0.00
R 1000 5060 1 561096.004798313.40 0.00
R 1000 5064 1 561177.904798370.80 0.00
R 1000 5068 1 561259.804798428.10 0.00
R 1000 5072 1 561341.704798485.50 0.00
R 1000 5076 1 561423.604798542.80 0.00
R 1000 5080 1 561505.504798600.20 0.00
R 1000 5084 1 561587.404798657.60 0.00
R 1000 5088 1 561669.404798714.90 0.00
R 1000 5092 1 561751.304798772.30 0.00
R 1000 5096 1 561833.204798829.60 0.00
R 1012 5000 1 560039.304797207.30 0.00
R 1012 5004 1 560121.204797264.70 0.00
R 1012 5008 1 560203.104797322.00 0.00
R 1012 5012 1 560285.004797379.40 0.00
R 1012 5016 1 560367.004797436.70 0.00
R 1012 5020 1 560448.904797494.10 0.00
R 1012 5024 1 560530.804797551.40 0.00
R 1012 5028 1 560612.704797608.80 0.00
R 1012 5032 1 560694.604797666.20 0.00
R 1012 5036 1 560776.504797723.50 0.00
R 1012 5040 1 560858.504797780.90 0.00
R 1012 5044 1 560940.404797838.20 0.00
R 1012 5048 1 561022.304797895.60 0.00
R 1012 5052 1 561104.204797953.00 0.00
R 1012 5056 1 561186.104798010.30 0.00
R 1012 5060 1 561268.004798067.70 0.00
R 1012 5064 1 561349.904798125.00 0.00
R 1012 5068 1 561431.904798182.40 0.00
R 1012 5072 1 561513.804798239.70 0.00
R 1012 5076 1 561595.704798297.10 0.00
R 1012 5080 1 561677.604798354.50 0.00
R 1012 5084 1 561759.504798411.80 0.00
R 1012 5088 1 561841.404798469.20 0.00
R 1012 5092 1 561923.304798526.50 0.00
R 1012 5096 1 562005.304798583.90 0.00
R 1024 5000 1 560211.404796961.60 0.00
R 1024 5004 1 560293.304797018.90 0.00
R 1024 5008 1 560375.204797076.30 0.00
R 1024 5012 1 560457.104797133.60 0.00
R 1024 5016 1 560539.004797191.00 0.00
R 1024 5020 1 560620.904797248.30 0.00
R 1024 5024 1 560702.904797305.70 0.00
R 1024 5028 1 560784.804797363.10 0.00
R 1024 5032 1 560866.704797420.40 0.00
R 1024 5036 1 560948.604797477.80 0.00
R 1024 5040 1 561030.504797535.10 0.00
R 1024 5044 1 561112.404797592.50 0.00
R 1024 5048 1 561194.404797649.80 0.00
R 1024 5052 1 561276.304797707.20 0.00
R 1024 5056 1 561358.204797764.60 0.00
R 1024 5060 1 561440.104797821.90 0.00
R 1024 5064 1 561522.004797879.30 0.00
R 1024 5068 1 561603.904797936.60 0.00
R 1024 5072 1 561685.804797994.00 0.00
R 1024 5076 1 561767.804798051.40 0.00
R 1024 5080 1 561849.704798108.70 0.00
R 1024 5084 1 561931.604798166.10 0.00
R 1024 5088 1 562013.504798223.40 0.00
R 1024 5092 1 562095.404798280.80 0.00
R 1024 5096 1 562177.304798338.10 0.00
R 1036 5000 1 560383.404796715.80 0.00
R 1036 5004 1 560465.404796773.20 0.00
R 1036 5008 1 560547.304796830.50 0.00
R 1036 5012 1 560629.204796887.90 0.00
R 1036 5016 1 560711.104796945.20 0.00
R 1036 5020 1 560793.004797002.60 0.00
R 1036 5024 1 560874.904797060.00 0.00
R 1036 5028 1 560956.904797117.30 0.00
R 1036 5032 1 561038.804797174.70 0.00
R 1036 5036 1 561120.704797232.00 0.00
R 1036 5040 1 561202.604797289.40 0.00
R 1036 5044 1 561284.504797346.70 0.00
R 1036 5048 1 561366.404797404.10 0.00
R 1036 5052 1 561448.304797461.50 0.00
R 1036 5056 1 561530.304797518.80 0.00
R 1036 5060 1 561612.204797576.20 0.00
R 1036 5064 1 561694.104797633.50 0.00
R 1036 5068 1 561776.004797690.90 0.00
R 1036 5072 1 561857.904797748.20 0.00
R 1036 5076 1 561939.804797805.60 0.00
R 1036 5080 1 562021.704797863.00 0.00
R 1036 5084 1 562103.704797920.30 0.00
R 1036 5088 1 562185.604797977.70 0.00
R 1036 5092 1 562267.504798035.00 0.00
R 1036 5096 1 562349.404798092.40 0.00
R 1048 5000 1 560555.504796470.10 0.00
R 1048 5004 1 560637.404796527.40 0.00
R 1048 5008 1 560719.304796584.80 0.00
R 1048 5012 1 560801.304796642.10 0.00
R 1048 5016 1 560883.204796699.50 0.00
R 1048 5020 1 560965.104796756.90 0.00
R 1048 5024 1 561047.004796814.20 0.00
R 1048 5028 1 561128.904796871.60 0.00
R 1048 5032 1 561210.804796928.90 0.00
R 1048 5036 1 561292.804796986.30 0.00
R 1048 5040 1 561374.704797043.60 0.00
R 1048 5044 1 561456.604797101.00 0.00
R 1048 5048 1 561538.504797158.40 0.00
R 1048 5052 1 561620.404797215.70 0.00
R 1048 5056 1 561702.304797273.10 0.00
R 1048 5060 1 561784.204797330.40 0.00
R 1048 5064 1 561866.204797387.80 0.00
R 1048 5068 1 561948.104797445.10 0.00
R 1048 5072 1 562030.004797502.50 0.00
R 1048 5076 1 562111.904797559.90 0.00
R 1048 5080 1 562193.804797617.20 0.00
R 1048 5084 1 562275.704797674.60 0.00
R 1048 5088 1 562357.704797731.90 0.00
R 1048 5092 1 562439.604797789.30 0.00
R 1048 5096 1 562521.504797846.60 0.00
R 1060 5000 1 560727.604796224.30 0.00
R 1060 5004 1 560809.504796281.70 0.00
R 1060 5008 1 560891.404796339.00 0.00
R 1060 5012 1 560973.304796396.40 0.00
R 1060 5016 1 561055.304796453.70 0.00
R 1060 5020 1 561137.204796511.10 0.00
R 1060 5024 1 561219.104796568.50 0.00
R 1060 5028 1 561301.004796625.80 0.00
R 1060 5032 1 561382.904796683.20 0.00
R 1060 5036 1 561464.804796740.50 0.00
R 1060 5040 1 561546.704796797.90 0.00
R 1060 5044 1 561628.704796855.30 0.00
R 1060 5048 1 561710.604796912.60 0.00
R 1060 5052 1 561792.504796970.00 0.00
R 1060 5056 1 561874.404797027.30 0.00
R 1060 5060 1 561956.304797084.70 0.00
R 1060 5064 1 562038.204797142.00 0.00
R 1060 5068 1 562120.104797199.40 0.00
R 1060 5072 1 562202.104797256.80 0.00
R 1060 5076 1 562284.004797314.10 0.00
R 1060 5080 1 562365.904797371.50 0.00
R 1060 5084 1 562447.804797428.80 0.00
R 1060 5088 1 562529.704797486.20 0.00
R 1060 5092 1 562611.604797543.50 0.00
R 1060 5096 1 562693.604797600.90 0.00
R 1072 5000 1 560899.704795978.60 0.00
R 1072 5004 1 560981.604796035.90 0.00
R 1072 5008 1 561063.504796093.30 0.00
R 1072 5012 1 561145.404796150.60 0.00
R 1072 5016 1 561227.304796208.00 0.00
R 1072 5020 1 561309.204796265.40 0.00
R 1072 5024 1 561391.204796322.70 0.00
R 1072 5028 1 561473.104796380.10 0.00
R 1072 5032 1 561555.004796437.40 0.00
R 1072 5036 1 561636.904796494.80 0.00
R 1072 5040 1 561718.804796552.20 0.00
R 1072 5044 1 561800.704796609.50 0.00
R 1072 5048 1 561882.604796666.90 0.00
R 1072 5052 1 561964.604796724.20 0.00
R 1072 5056 1 562046.504796781.60 0.00
R 1072 5060 1 562128.404796838.90 0.00
R 1072 5064 1 562210.304796896.30 0.00
R 1072 5068 1 562292.204796953.70 0.00
R 1072 5072 1 562374.104797011.00 0.00
R 1072 5076 1 562456.104797068.40 0.00
R 1072 5080 1 562538.004797125.70 0.00
R 1072 5084 1 562619.904797183.10 0.00
R 1072 5088 1 562701.804797240.40 0.00
R 1072 5092 1 562783.704797297.80 0.00
R 1072 5096 1 562865.604797355.20 0.00
R 1084 5000 1 561071.704795732.80 0.00
R 1084 5004 1 561153.704795790.20 0.00
R 1084 5008 1 561235.604795847.50 0.00
R 1084 5012 1 561317.504795904.90 0.00
R 1084 5016 1 561399.404795962.30 0.00
R 1084 5020 1 561481.304796019.60 0.00
R 1084 5024 1 561563.204796077.00 0.00
R 1084 5028 1 561645.104796134.30 0.00
R 1084 5032 1 561727.104796191.70 0.00
R 1084 5036 1 561809.004796249.00 0.00
R 1084 5040 1 561890.904796306.40 0.00
R 1084 5044 1 561972.804796363.80 0.00
R 1084 5048 1 562054.704796421.10 0.00
R 1084 5052 1 562136.604796478.50 0.00
R 1084 5056 1 562218.504796535.80 0.00
R 1084 5060 1 562300.504796593.20 0.00
R 1084 5064 1 562382.404796650.60 0.00
R 1084 5068 1 562464.304796707.90 0.00
R 1084 5072 1 562546.204796765.30 0.00
R 1084 5076 1 562628.104796822.60 0.00
R 1084 5080 1 562710.004796880.00 0.00
R 1084 5084 1 562792.004796937.30 0.00
R 1084 5088 1 562873.904796994.70 0.00
R 1084 5092 1 562955.804797052.10 0.00
R 1084 5096 1 563037.704797109.40 0.00

7216
data/docs/SeteSxPreplots.s01 Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
{
"timeline": [
{
"date": "2020-08-08",
"event": "D\u00e9but enregistrements continus OBN",
"type": "start"
},
{
"date": "2020-08-15",
"event": "D\u00e9ploiement AUV - Zone principale",
"type": "operation"
},
{
"date": "2020-08-24",
"event": "Premiers fichiers RAW g\u00e9n\u00e9r\u00e9s",
"type": "data"
},
{
"date": "2020-09-10",
"event": "Phase AUV intensive - Couverture maximale",
"type": "operation"
},
{
"date": "2020-09-18",
"event": "R\u00e9cup\u00e9ration finale des AUV + OBN",
"type": "milestone"
},
{
"date": "2020-09-21",
"event": "R\u00e9cup\u00e9ration AUV zones difficiles (Trawler)",
"type": "operation"
},
{
"date": "2020-09-22",
"event": "Derni\u00e8res zones AUV Remaining",
"type": "operation"
},
{
"date": "2020-09-23",
"event": "Fin de campagne - Tous \u00e9quipements r\u00e9cup\u00e9r\u00e9s",
"type": "end"
}
],
"documents": [
{
"name": "GUNDALF Array Report",
"file": "S320A08a10-GUNDALF array modelling suite - Array report.pdf",
"category": "Sources"
},
{
"name": "Acquisition Parameters",
"file": "SBGS_Sete_SpiceRack_Acquisition_Parameters_Spreadsheet.xlsx",
"category": "Acquisition"
},
{
"name": "Technical Specs OBN",
"file": "SBGS Standard Technical Specs for Node Surveys v10.docx",
"category": "Specifications"
},
{
"name": "Source Preplots (SPS)",
"file": "SeteSxPreplots.s01",
"category": "Geometry"
},
{
"name": "Receiver Preplots (SPS)",
"file": "SeteRxPreplots.r01",
"category": "Geometry"
},
{
"name": "Final PickUp Map",
"file": "Final_PickUp_20200918.png",
"category": "Maps"
}
],
"snapshots": [
{
"date": "2020-09-18",
"name": "Final_PickUp_918",
"file": "Final_PickUp_20200918.png"
},
{
"date": "2020-09-18-Copier(1)",
"name": "Final_PickUp_918-Copier(1)",
"file": "Final_PickUp_20200918-Copier(1).png"
},
{
"date": "2020-09-22",
"name": "Sete_AUV_Remaining_922",
"file": "Sete_AUV_Remaining_20200922.png"
},
{
"date": "2020-09-22-Copier(1)",
"name": "Sete_AUV_Remaining_922-Copier(1)",
"file": "Sete_AUV_Remaining_20200922-Copier(1).png"
},
{
"date": "2020-09-22bis",
"name": "Sete_AUV_Remaining_922bis",
"file": "Sete_AUV_Remaining_20200922bis.png"
},
{
"date": "2020-09-21",
"name": "Sete_AUV_Trawler_921",
"file": "Sete_AUV_Trawler_20200921.png"
},
{
"date": "2020-09-22",
"name": "Sete_AUV_Trawler_922",
"file": "Sete_AUV_Trawler_20200922.png"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

438
data/index.json Executable file
View File

@@ -0,0 +1,438 @@
{
"nodes": {
"221": {
"position": {
"easting": 560100.0,
"northing": 4795500.0,
"depth": 35.0
},
"files": [
{
"filename": "auto_221_145435_b0_rsn80274_seq1_1596898430.h5",
"date": "2020-08-08",
"timestamp": 1596898430,
"duration_sec": 20.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 0.16
}
]
},
"224": {
"position": {
"easting": 560500.0,
"northing": 4795800.0,
"depth": 40.0
},
"files": [
{
"filename": "auto_224_160921_b12_rsn2221_seq1_1597159766.h5",
"date": "2020-08-11",
"timestamp": 1597159766,
"duration_sec": 2390.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 24.44
},
{
"filename": "auto_224_161538_b25_rsn3541_seq1_1597159797.h5",
"date": "2020-08-11",
"timestamp": 1597159797,
"duration_sec": 2740.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 27.69
},
{
"filename": "auto_224_162759_b17_rsn21093_seq1_1597159798.h5",
"date": "2020-08-11",
"timestamp": 1597159798,
"duration_sec": 3480.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 37.62
}
]
},
"225": {
"position": {
"easting": 560900.0,
"northing": 4796100.0,
"depth": 45.0
},
"files": [
{
"filename": "auto_225_173539_b103_rsn51079_seq1_1597252375.h5",
"date": "2020-08-12",
"timestamp": 1597252375,
"duration_sec": 1360.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 15.44
},
{
"filename": "auto_225_173616_b77_rsn2690_seq1_1597250647.h5",
"date": "2020-08-12",
"timestamp": 1597250647,
"duration_sec": 3120.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 35.27
},
{
"filename": "auto_225_173619_b105_rsn4162_seq1_1597252399.h5",
"date": "2020-08-12",
"timestamp": 1597252399,
"duration_sec": 1370.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 15.49
},
{
"filename": "auto_225_173619_b56_rsn2189_seq1_1597238700.h5",
"date": "2020-08-12",
"timestamp": 1597238700,
"duration_sec": 15070.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 167.57
},
{
"filename": "auto_225_173619_b82_rsn72636_seq1_1597250071.h5",
"date": "2020-08-12",
"timestamp": 1597250071,
"duration_sec": 3700.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 41.34
},
{
"filename": "auto_225_173623_b21_rsn12296_seq1_1597227594.h5",
"date": "2020-08-12",
"timestamp": 1597227594,
"duration_sec": 26180.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 289.17
},
{
"filename": "auto_225_173623_b95_rsn80274_seq1_1597244605.h5",
"date": "2020-08-12",
"timestamp": 1597244605,
"duration_sec": 9170.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 102.01
},
{
"filename": "auto_225_173630_b65_rsn12358_seq1_1597238705.h5",
"date": "2020-08-12",
"timestamp": 1597238705,
"duration_sec": 15080.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 163.32
},
{
"filename": "auto_225_173634_b2_rsn71476_seq1_1597217439.h5",
"date": "2020-08-12",
"timestamp": 1597217439,
"duration_sec": 36350.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 391.88
},
{
"filename": "auto_225_173634_b50_rsn51848_seq1_1597238697.h5",
"date": "2020-08-12",
"timestamp": 1597238697,
"duration_sec": 15090.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 165.26
}
]
},
"228": {
"position": {
"easting": 561300.0,
"northing": 4796400.0,
"depth": 42.0
},
"files": [
{
"filename": "auto_228_063604_b16_rsn5819_seq1_1597419185.h5",
"date": "2020-08-14",
"timestamp": 1597419185,
"duration_sec": 54170.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 567.4
},
{
"filename": "auto_228_063604_b194_rsn3965_seq1_1597411770.h5",
"date": "2020-08-14",
"timestamp": 1597411770,
"duration_sec": 61590.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 691.99
},
{
"filename": "auto_228_063605_b77_rsn2690_seq1_1597256283.h5",
"date": "2020-08-12",
"timestamp": 1597256283,
"duration_sec": 217070.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2460.93
},
{
"filename": "auto_228_064125_b26_rsn2128_seq1_1597414685.h5",
"date": "2020-08-14",
"timestamp": 1597414685,
"duration_sec": 58990.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 625.23
},
{
"filename": "auto_228_064125_b82_rsn72636_seq1_1597256285.h5",
"date": "2020-08-12",
"timestamp": 1597256285,
"duration_sec": 217390.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2415.92
},
{
"filename": "auto_228_064126_b56_rsn2189_seq1_1597256285.h5",
"date": "2020-08-12",
"timestamp": 1597256285,
"duration_sec": 217390.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2479.67
},
{
"filename": "auto_228_064126_b90_rsn2011_seq1_1597430558.h5",
"date": "2020-08-14",
"timestamp": 1597430558,
"duration_sec": 43120.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 469.27
},
{
"filename": "auto_228_064131_b187_rsn80304_seq1_1597408779.h5",
"date": "2020-08-14",
"timestamp": 1597408779,
"duration_sec": 64900.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 727.37
},
{
"filename": "auto_228_064131_b3_rsn4589_seq1_1597419457.h5",
"date": "2020-08-14",
"timestamp": 1597419457,
"duration_sec": 54230.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 608.76
},
{
"filename": "auto_228_064131_b65_rsn12358_seq1_1597256199.h5",
"date": "2020-08-12",
"timestamp": 1597256199,
"duration_sec": 217480.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2405.57
},
{
"filename": "auto_228_064210_b50_rsn51848_seq1_1597256250.h5",
"date": "2020-08-12",
"timestamp": 1597256250,
"duration_sec": 217470.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2444.06
},
{
"filename": "auto_228_064212_b29_rsn2648_seq1_1597414690.h5",
"date": "2020-08-14",
"timestamp": 1597414690,
"duration_sec": 59030.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 622.02
},
{
"filename": "auto_228_064254_b21_rsn12296_seq1_1597256218.h5",
"date": "2020-08-12",
"timestamp": 1597256218,
"duration_sec": 217550.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 2398.41
},
{
"filename": "auto_228_064256_b181_rsn6523_seq1_1597432534.h5",
"date": "2020-08-14",
"timestamp": 1597432534,
"duration_sec": 41240.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 451.2
},
{
"filename": "auto_228_175058_b130_rsn3747_seq1_1597513819.h5",
"date": "2020-08-15",
"timestamp": 1597513819,
"duration_sec": 30.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 0.36
},
{
"filename": "auto_228_180741_b130_rsn3747_seq1_1597514438.h5",
"date": "2020-08-15",
"timestamp": 1597514438,
"duration_sec": 420.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 4.71
}
]
},
"229": {
"position": {
"easting": 560700.0,
"northing": 4796700.0,
"depth": 38.0
},
"files": [
{
"filename": "auto_229_132551_b128_rsn4049_seq1_1597571215.h5",
"date": "2020-08-16",
"timestamp": 1597571215,
"duration_sec": 3600.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 40.72
},
{
"filename": "auto_229_132815_b128_rsn4049_seq1_1597571215.h5",
"date": "2020-08-16",
"timestamp": 1597571215,
"duration_sec": 3600.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 40.72
},
{
"filename": "auto_229_142731_b21_rsn12296_seq1_1597475138.h5",
"date": "2020-08-15",
"timestamp": 1597475138,
"duration_sec": 112910.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1156.73
}
]
},
"230": {
"position": {
"easting": 561100.0,
"northing": 4795200.0,
"depth": 50.0
},
"files": [
{
"filename": "auto_230_063854_b12_rsn21524_seq1_1597516594.h5",
"date": "2020-08-15",
"timestamp": 1597516594,
"duration_sec": 129730.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1409.06
},
{
"filename": "auto_230_064032_b10_rsn21475_seq1_1597516582.h5",
"date": "2020-08-15",
"timestamp": 1597516582,
"duration_sec": 129840.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1425.65
},
{
"filename": "auto_230_070712_b18_rsn80310_seq1_1597497040.h5",
"date": "2020-08-15",
"timestamp": 1597497040,
"duration_sec": 150980.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1660.22
},
{
"filename": "auto_230_070815_b27_rsn6157_seq1_1597516628.h5",
"date": "2020-08-15",
"timestamp": 1597516628,
"duration_sec": 131460.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1357.8
},
{
"filename": "auto_230_070838_b16_rsn5819_seq1_1597475191.h5",
"date": "2020-08-15",
"timestamp": 1597475191,
"duration_sec": 172920.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1813.61
},
{
"filename": "auto_230_073833_b23_rsn3078_seq1_1597498598.h5",
"date": "2020-08-15",
"timestamp": 1597498598,
"duration_sec": 151310.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 1621.03
}
]
},
"289": {
"position": {
"easting": 560300.0,
"northing": 4796900.0,
"depth": 33.0
},
"files": [
{
"filename": "auto_289_090651_b0_rsn4410_seq1_1571216697.h5",
"date": "2019-10-16",
"timestamp": 1571216697,
"duration_sec": 30.0,
"sample_rate": 500,
"channels": 4,
"size_mb": 0.26
}
]
}
},
"dates": [
"2019-10-16",
"2020-08-08",
"2020-08-11",
"2020-08-12",
"2020-08-14",
"2020-08-15",
"2020-08-16"
],
"generated": "2026-02-05T15:05:56.229887",
"total_files": 40
}

276
frontend_src/App.tsx Normal file
View File

@@ -0,0 +1,276 @@
import { useState, useEffect, useCallback } from 'react';
import { MapContainer, TileLayer } from 'react-leaflet';
import NodeMarkers from './components/NodeMarkers';
import Sidebar from './components/Sidebar';
import SeismicSection from './SeismicSection';
import H5Coverage from './components/H5Coverage';
import DataDocumentation from './components/DataDocumentation';
import H5Dashboard from './components/H5Dashboard';
import CampaignDocs from "./components/CampaignDocs";
import { Node, DataWindow } from './types';
const API_BASE = '/seismic/api';
const DEFAULT_CENTER: [number, number] = [43.40, 3.70];
function App() {
const [nodes, setNodes] = useState<Node[]>([]);
const [dates, setDates] = useState<string[]>([]);
const [selectedDate, setSelectedDate] = useState<string>('');
const [selectedChannel, setSelectedChannel] = useState<string>('ch0');
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
const [currentTime, setCurrentTime] = useState<number>(0);
const [dataWindow, setDataWindow] = useState<DataWindow | null>(null);
const [loading, setLoading] = useState(true);
const [sampleRate, setSampleRate] = useState(200);
const [showOnlyWithData, setShowOnlyWithData] = useState(false);
const [rmsTimeline, setRmsTimeline] = useState<any>(null);
const [allAdcValues, setAllAdcValues] = useState<Record<string, number>>({});
const [isPlaying, setIsPlaying] = useState(false);
const [playSpeed, setPlaySpeed] = useState(1);
const [chatOpen, setChatOpen] = useState(false);
const [chatMsg, setChatMsg] = useState('');
const [chatHistory, setChatHistory] = useState<any[]>([]);
const [showSection, setShowSection] = useState(false);
const [showDashboard, setShowDashboard] = useState(false);
const [showCampaignDocs, setShowCampaignDocs] = useState(false);
const [showDocumentation, setShowDocumentation] = useState(false);
const [showCoverage, setShowCoverage] = useState(false);
const [migrationStatus, setMigrationStatus] = useState<any>(null);
// 🔗 Deep-linking: Lire les paramètres URL au démarrage
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const urlDate = params.get('date');
const urlChannel = params.get('channel');
const urlTime = params.get('time');
const urlNode = params.get('node');
if (urlDate) setSelectedDate(urlDate);
if (urlChannel) setSelectedChannel(urlChannel);
if (urlTime) setCurrentTime(parseFloat(urlTime));
if (urlNode && nodes.length > 0) {
const node = nodes.find(n => n.id === urlNode);
if (node) setSelectedNode(node);
}
}, [nodes]); // Se déclenche quand les nodes sont chargés
// 🔗 Deep-linking: Synchroniser l'URL quand les états changent
useEffect(() => {
const params = new URLSearchParams();
if (selectedDate) params.set('date', selectedDate);
if (selectedChannel) params.set('channel', selectedChannel);
if (currentTime > 1000000) params.set('time', currentTime.toString());
if (selectedNode) params.set('node', selectedNode.id);
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.pushState({}, '', newUrl);
}, [selectedDate, selectedChannel, currentTime, selectedNode]);
useEffect(() => {
fetch(`${API_BASE}/nodes`).then(r => r.json()).then(data => {
setNodes(data.nodes || []);
setSampleRate(data.sampleRateHz || 200);
setLoading(false);
});
}, []);
useEffect(() => {
fetch(`${API_BASE}/dates`).then(r => r.json()).then(data => {
const dateList = data.dates || [];
setDates(dateList);
if (dateList.length > 0 && !selectedDate) setSelectedDate(dateList[dateList.length - 1]);
});
}, []);
// Polling de la migration
useEffect(() => {
const checkStatus = () => {
fetch(`${API_BASE}/migration-status`)
.then(r => r.json())
.then(data => setMigrationStatus(data))
.catch(() => {});
};
const interval = setInterval(checkStatus, 5000);
checkStatus();
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (!selectedDate || !selectedChannel) return;
setRmsTimeline(null);
fetch(`${API_BASE}/rms-timeline?date=${selectedDate}&channel=${selectedChannel}`)
.then(res => res.json())
.then(data => {
if (data && data.nodes && Object.keys(data.nodes).length > 0) {
setRmsTimeline(data);
const allPts = Object.values(data.nodes).flat() as any[];
if (allPts.length > 0) {
const minTs = Math.min(...allPts.map(p => p.ts));
if (minTs > 1000000) setCurrentTime(minTs);
}
} else {
const ts = new Date(selectedDate).getTime() / 1000;
if (!isNaN(ts)) setCurrentTime(ts);
}
});
}, [selectedDate, selectedChannel]);
useEffect(() => {
if (!isPlaying) return;
const interval = setInterval(() => {
setCurrentTime(prev => prev + (60 * (playSpeed / 5)));
}, 200);
return () => clearInterval(interval);
}, [isPlaying, playSpeed]);
useEffect(() => {
if (!rmsTimeline || !rmsTimeline.nodes || !currentTime) {
setAllAdcValues({});
return;
}
const values: Record<string, number> = {};
Object.entries(rmsTimeline.nodes).forEach(([nodeId, dataPoints]) => {
const pts = dataPoints as any[];
const point = pts.find(p => p.ts >= currentTime) || pts[pts.length - 1];
if (point) values[nodeId] = point.rms;
});
setAllAdcValues(values);
}, [currentTime, rmsTimeline]);
const loadNodeData = useCallback(async () => {
if (!selectedNode || !selectedDate || currentTime < 1000000) return;
setLoading(true);
try {
const res = await fetch(`${API_BASE}/data?node=${selectedNode.id}&date=${selectedDate}&channel=${selectedChannel}&start=${Math.floor(currentTime)}`);
const data = await res.json();
if (data && data.samples) setDataWindow({ ...data, node: selectedNode.id, date: selectedDate, channel: selectedChannel });
else setDataWindow(null);
} catch (e) { setDataWindow(null); }
setLoading(false);
}, [selectedNode, selectedDate, selectedChannel, currentTime]);
useEffect(() => { loadNodeData(); }, [selectedNode, selectedDate, selectedChannel]);
const formatTime = (ts: number) => {
if (ts < 1000000) return '--:--:--';
return new Date(ts * 1000).toISOString().substr(11, 8);
};
const sendChat = async () => {
if (!chatMsg) return;
const newHist = [...chatHistory, { role: 'user', text: chatMsg }];
setChatHistory(newHist);
setChatMsg('');
try {
const res = await fetch(`${API_BASE}/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: chatMsg })
});
const data = await res.json();
setChatHistory([...newHist, { role: 'agent', text: data.response }]);
} catch (e) { setChatHistory([...newHist, { role: 'agent', text: "Erreur agent." }]); }
};
return (<>
<div className="app-container" style={{ display: 'flex', flexDirection: 'column', height: '100vh', width: '100vw', background: '#0f172a', color: '#fff' }}>
<header style={{ height: '60px', display: 'flex', alignItems: 'center', padding: '0 20px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
<h1 style={{ margin: 0, fontSize: '1.2rem' }}>Seismic Viewer</h1>
{migrationStatus && migrationStatus.processed_files < migrationStatus.total_files && (
<div style={{ marginLeft: '20px', background: '#334155', padding: '5px 10px', borderRadius: '15px', fontSize: '0.75rem', border: '1px solid #4ade80' }}>
<span style={{ color: '#4ade80', fontWeight: 'bold' }}> Migration : </span>
{Math.round((migrationStatus.processed_files / migrationStatus.total_files) * 100)}%
({migrationStatus.processed_files}/{migrationStatus.total_files})
</div>
)}
<div style={{ color: '#94a3b8', fontSize: '0.8rem', marginLeft: '20px' }}>
Nodes: {nodes.length} | Actifs: {nodes.filter(n=>n.hasDates).length}
<button onClick={() => setShowDashboard(true)} style={{ background: '#f59e0b', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold' }}>🎯 Dashboard H5</button>
<button onClick={() => setShowCampaignDocs(true)} style={{ background: "#8b5cf6", border: "none", color: "#fff", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold", fontSize: "0.9rem" }}>📚 Documentation Campagne</button>
</div>
<div style={{ marginLeft: 'auto', display: 'flex', gap: '10px' }}>
<button onClick={() => setShowSection(true)} style={{ background: '#8b5cf6', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>Vue Globale</button>
<button onClick={() => setShowDocumentation(true)} style={{ background: '#10b981', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>📖 Documentation</button>
<button onClick={() => setShowCoverage(true)} style={{ background: '#3b82f6', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>Coverage H5</button>
<select value={selectedDate} onChange={e => setSelectedDate(e.target.value)} style={{ background: '#0f172a', color: '#fff', border: '1px solid #334155' }}>
{dates.map(d => <option key={d} value={d}>{d}</option>)}
</select>
<select value={selectedChannel} onChange={e => setSelectedChannel(e.target.value)} style={{ background: '#0f172a', color: '#fff', border: '1px solid #334155' }}>
{['ch0', 'ch1', 'ch2', 'ch3'].map(ch => <option key={ch} value={ch}>{ch.toUpperCase()}</option>)}
</select>
</div>
</header>
<div style={{ height: '50px', background: '#0f172a', display: 'flex', alignItems: 'center', padding: '0 20px', gap: '15px', borderBottom: '1px solid #334155' }}>
<button onClick={() => setIsPlaying(!isPlaying)} aria-label={isPlaying ? 'Mettre en pause' : 'Lancer la lecture'} style={{ background: isPlaying ? '#ef4444' : '#22c55e', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', fontWeight: 'bold', cursor: 'pointer' }}>
{isPlaying ? 'PAUSE' : 'PLAY'}
</button>
<div style={{ display: 'flex', gap: '2px', background: '#1e293b', padding: '2px', borderRadius: '4px' }}>
{[1, 10, 100, 1000].map(s => (
<button key={s} onClick={() => setPlaySpeed(s)} aria-label={`Vitesse ${s}x`} style={{ background: playSpeed === s ? '#3b82f6' : 'transparent', color: '#fff', border: 'none', padding: '2px 8px', fontSize: '0.7rem', cursor: 'pointer' }}>x{s}</button>
))}
</div>
<span style={{ fontFamily: 'monospace', color: '#4ade80', fontSize: '1.1rem', minWidth: '80px' }}>{formatTime(currentTime)}</span>
<input type="range" aria-label="Curseur temporel" style={{ flex: 1 }} min={currentTime - 1800} max={currentTime + 1800} value={currentTime} onChange={e => { setCurrentTime(Number(e.target.value)); setIsPlaying(false); }} />
</div>
<main style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
<div style={{ flex: 1, position: 'relative' }}>
<MapContainer center={DEFAULT_CENTER} zoom={13} style={{ height: '100%', width: '100%' }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<NodeMarkers nodes={nodes} selectedNode={selectedNode} onSelectNode={setSelectedNode} adcValues={allAdcValues} showOnlyWithData={showOnlyWithData} />
</MapContainer>
</div>
<Sidebar selectedNode={selectedNode} dataWindow={dataWindow} loading={loading} sampleRate={sampleRate} />
</main>
<SeismicSection nodes={nodes} currentTime={currentTime} channel={selectedChannel} visible={showSection} onClose={() => setShowSection(false)} />
<div style={{ position: 'fixed', bottom: '20px', right: '20px', zIndex: 10000 }}>
{!chatOpen ? (
<button onClick={() => setChatOpen(true)} aria-label="Ouvrir le chat assistant" style={{ width: '50px', height: '50px', borderRadius: '25px', background: '#e94560', border: 'none', color: '#fff', fontSize: '20px', cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.3)' }}>💬</button>
) : (
<div style={{ background: '#1e293b', width: '300px', height: '400px', borderRadius: '8px', display: 'flex', flexDirection: 'column', border: '1px solid #334155', boxShadow: '0 8px 24px rgba(0,0,0,0.4)' }}>
<div style={{ padding: '10px', background: '#334155', display: 'flex', justifyContent: 'space-between', borderRadius: '8px 8px 0 0' }}>
<span style={{ fontSize: '0.8rem', fontWeight: 'bold' }}>Assistant</span>
<button onClick={() => setChatOpen(false)} aria-label="Fermer le chat" style={{ background: 'none', border: 'none', color: '#fff', cursor: 'pointer' }}>×</button>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }}>
{chatHistory.map((m, i) => (
<div key={i} style={{ alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start', background: m.role === 'user' ? '#3b82f6' : '#475569', padding: '6px 10px', borderRadius: '8px', fontSize: '0.8rem', maxWidth: '85%' }}>{m.text}</div>
))}
</div>
<div style={{ padding: '10px', borderTop: '1px solid #334155', display: 'flex', gap: '5px' }}>
<input value={chatMsg} onChange={e => setChatMsg(e.target.value)} onKeyPress={e => e.key === 'Enter' && sendChat()} aria-label="Poser une question" style={{ flex: 1, background: '#0f172a', border: '1px solid #334155', color: '#fff', padding: '5px', borderRadius: '4px', fontSize: '0.8rem' }} placeholder="Question…" />
<button onClick={sendChat} style={{ background: '#e94560', border: 'none', color: '#fff', padding: '5px 10px', borderRadius: '4px', fontSize: '0.8rem' }}>OK</button>
</div>
</div>
)}
</div>
{showDashboard && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowDashboard(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}> Fermer Dashboard</button>
<H5Dashboard />
</div>
</div>
)}
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}> Fermer</button>
<CampaignDocs />
</div>
</div>
)}
{showDocumentation && <DataDocumentation onClose={() => setShowDocumentation(false)} />}
{showCoverage && <H5Coverage onClose={() => setShowCoverage(false)} />}
</div>
</>
);
}
export default App;

346
frontend_src/App.tsx.backup Normal file
View File

@@ -0,0 +1,346 @@
import { useState, useEffect, useCallback } from 'react';
import { MapContainer, TileLayer } from 'react-leaflet';
import NodeMarkers from './components/NodeMarkers';
import Sidebar from './components/Sidebar';
import SeismicSection from './SeismicSection';
import H5Coverage from './components/H5Coverage';
import DataDocumentation from './components/DataDocumentation';
import H5Dashboard from './components/H5Dashboard';
import CampaignDocs from "./components/CampaignDocs";
import { Node, DataWindow } from './types';
const API_BASE = '/seismic/api';
const DEFAULT_CENTER: [number, number] = [43.40, 3.70];
function App() {
const [nodes, setNodes] = useState<Node[]>([]);
const [dates, setDates] = useState<string[]>([]);
const [selectedDate, setSelectedDate] = useState<string>('');
const [selectedChannel, setSelectedChannel] = useState<string>('ch0');
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
const [currentTime, setCurrentTime] = useState<number>(0);
const [dataWindow, setDataWindow] = useState<DataWindow | null>(null);
const [loading, setLoading] = useState(true);
const [sampleRate, setSampleRate] = useState(200);
const [showOnlyWithData, setShowOnlyWithData] = useState(false);
const [rmsTimeline, setRmsTimeline] = useState<any>(null);
const [allAdcValues, setAllAdcValues] = useState<Record<string, number>>({});
const [isPlaying, setIsPlaying] = useState(false);
const [playSpeed, setPlaySpeed] = useState(1);
const [chatOpen, setChatOpen] = useState(false);
const [chatMsg, setChatMsg] = useState('');
const [chatHistory, setChatHistory] = useState<any[]>([]);
const [showSection, setShowSection] = useState(false);
const [showDashboard, setShowDashboard] = useState(false);
const [showCampaignDocs, setShowCampaignDocs] = useState(false);
const [showDocumentation, setShowDocumentation] = useState(false);
const [showCoverage, setShowCoverage] = useState(false);
const [migrationStatus, setMigrationStatus] = useState<any>(null);
// 🔗 Deep-linking: Lire les paramètres URL au démarrage
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const urlDate = params.get('date');
const urlChannel = params.get('channel');
const urlTime = params.get('time');
if (urlDate) setSelectedDate(urlDate);
if (urlChannel) setSelectedChannel(urlChannel);
if (urlTime) setCurrentTime(parseFloat(urlTime));
if (urlNode && nodes.length > 0) {
const node = nodes.find(n => n.id === urlNode);
if (node) setSelectedNode(node);
}
}, [nodes]); // Se déclenche quand les nodes sont chargés
// 🔗 Deep-linking: Synchroniser l'URL quand les états changent
useEffect(() => {
const params = new URLSearchParams();
if (selectedDate) params.set('date', selectedDate);
if (selectedChannel) params.set('channel', selectedChannel);
if (currentTime > 1000000) params.set('time', currentTime.toString());
if (selectedNode) params.set('node', selectedNode.id);
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.pushState({}, '', newUrl);
}, [selectedDate, selectedChannel, currentTime, selectedNode]);
useEffect(() => {
fetch(`${API_BASE}/nodes`).then(r => r.json()).then(data => {
setNodes(data.nodes || []);
setSampleRate(data.sampleRateHz || 200);
setLoading(false);
});
}, []);
useEffect(() => {
fetch(`${API_BASE}/dates`).then(r => r.json()).then(data => {
const dateList = data.dates || [];
setDates(dateList);
if (dateList.length > 0 && !selectedDate) setSelectedDate(dateList[dateList.length - 1]);
});
}, []);
// Polling de la migration
useEffect(() => {
const checkStatus = () => {
fetch(`${API_BASE}/migration-status`)
.then(r => r.json())
.then(data => setMigrationStatus(data))
.catch(() => {});
};
const interval = setInterval(checkStatus, 5000);
checkStatus();
return () => clearInterval(interval);
}, []);
useEffect(() => {
if (!selectedDate || !selectedChannel) return;
setRmsTimeline(null);
fetch(`${API_BASE}/rms-timeline?date=${selectedDate}&channel=${selectedChannel}`)
.then(res => res.json())
.then(data => {
if (data && data.nodes && Object.keys(data.nodes).length > 0) {
setRmsTimeline(data);
const allPts = Object.values(data.nodes).flat() as any[];
if (allPts.length > 0) {
const minTs = Math.min(...allPts.map(p => p.ts));
if (minTs > 1000000) setCurrentTime(minTs);
}
} else {
const ts = new Date(selectedDate).getTime() / 1000;
if (!isNaN(ts)) setCurrentTime(ts);
}
});
}, [selectedDate, selectedChannel]);
useEffect(() => {
if (!isPlaying) return;
const interval = setInterval(() => {
setCurrentTime(prev => prev + (60 * (playSpeed / 5)));
}, 200);
return () => clearInterval(interval);
}, [isPlaying, playSpeed]);
useEffect(() => {
if (!rmsTimeline || !rmsTimeline.nodes || !currentTime) {
setAllAdcValues({});
return;
}
const values: Record<string, number> = {};
Object.entries(rmsTimeline.nodes).forEach(([nodeId, dataPoints]) => {
const pts = dataPoints as any[];
const point = pts.find(p => p.ts >= currentTime) || pts[pts.length - 1];
if (point) values[nodeId] = point.rms;
});
setAllAdcValues(values);
}, [currentTime, rmsTimeline]);
const loadNodeData = useCallback(async () => {
if (!selectedNode || !selectedDate || currentTime < 1000000) return;
setLoading(true);
try {
const res = await fetch(`${API_BASE}/data?node=${selectedNode.id}&date=${selectedDate}&channel=${selectedChannel}&start=${Math.floor(currentTime)}`);
const data = await res.json();
if (data && data.samples) setDataWindow({ ...data, node: selectedNode.id, date: selectedDate, channel: selectedChannel });
else setDataWindow(null);
} catch (e) { setDataWindow(null); }
setLoading(false);
}, [selectedNode, selectedDate, selectedChannel, currentTime]);
useEffect(() => { loadNodeData(); }, [selectedNode, selectedDate, selectedChannel]);
const formatTime = (ts: number) => {
if (ts < 1000000) return '--:--:--';
return new Date(ts * 1000).toISOString().substr(11, 8);
};
const sendChat = async () => {
if (!chatMsg) return;
const newHist = [...chatHistory, { role: 'user', text: chatMsg }];
setChatHistory(newHist);
setChatMsg('');
try {
const res = await fetch(`${API_BASE}/chat`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: chatMsg })
});
const data = await res.json();
setChatHistory([...newHist, { role: 'agent', text: data.response }]);
} catch (e) { setChatHistory([...newHist, { role: 'agent', text: "Erreur agent." }]); }
};
return (<>
<div className="app-container" style={{ display: 'flex', flexDirection: 'column', height: '100vh', width: '100vw', background: '#0f172a', color: '#fff' }}>
<header style={{ height: '60px', display: 'flex', alignItems: 'center', padding: '0 20px', background: '#1e293b', borderBottom: '1px solid #334155' }}>
<h1 style={{ margin: 0, fontSize: '1.2rem' }}>Seismic Viewer</h1>
{migrationStatus && migrationStatus.processed_files < migrationStatus.total_files && (
<div style={{ marginLeft: '20px', background: '#334155', padding: '5px 10px', borderRadius: '15px', fontSize: '0.75rem', border: '1px solid #4ade80' }}>
<span style={{ color: '#4ade80', fontWeight: 'bold' }}>⚡ Migration : </span>
{Math.round((migrationStatus.processed_files / migrationStatus.total_files) * 100)}%
({migrationStatus.processed_files}/{migrationStatus.total_files})
</div>
)}
<div style={{ color: '#94a3b8', fontSize: '0.8rem', marginLeft: '20px' }}>
Nodes: {nodes.length} | Actifs: {nodes.filter(n=>n.hasDates).length}
<button onClick={() => setShowDashboard(true)} style={{ background: '#f59e0b', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold' }}>🎯 Dashboard H5</button>
<button onClick={() => setShowCampaignDocs(true)} style={{ background: "#8b5cf6", border: "none", color: "#fff", padding: "8px 16px", borderRadius: "6px", cursor: "pointer", fontWeight: "bold", fontSize: "0.9rem" }}>📚 Documentation Campagne</button>
</div>
<div style={{ marginLeft: 'auto', display: 'flex', gap: '10px' }}>
<button onClick={() => setShowSection(true)} style={{ background: '#8b5cf6', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>Vue Globale</button>
<button onClick={() => setShowDocumentation(true)} style={{ background: '#10b981', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>📖 Documentation</button>
<button onClick={() => setShowCoverage(true)} style={{ background: '#3b82f6', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', cursor: 'pointer' }}>Coverage H5</button>
<select value={selectedDate} onChange={e => setSelectedDate(e.target.value)} style={{ background: '#0f172a', color: '#fff', border: '1px solid #334155' }}>
{dates.map(d => <option key={d} value={d}>{d}</option>)}
</select>
<select value={selectedChannel} onChange={e => setSelectedChannel(e.target.value)} style={{ background: '#0f172a', color: '#fff', border: '1px solid #334155' }}>
{['ch0', 'ch1', 'ch2', 'ch3'].map(ch => <option key={ch} value={ch}>{ch.toUpperCase()}</option>)}
</select>
</div>
</header>
<div style={{ height: '50px', background: '#0f172a', display: 'flex', alignItems: 'center', padding: '0 20px', gap: '15px', borderBottom: '1px solid #334155' }}>
<button onClick={() => setIsPlaying(!isPlaying)} aria-label={isPlaying ? 'Mettre en pause' : 'Lancer la lecture'} style={{ background: isPlaying ? '#ef4444' : '#22c55e', color: '#fff', border: 'none', padding: '5px 15px', borderRadius: '4px', fontWeight: 'bold', cursor: 'pointer' }}>
{isPlaying ? 'PAUSE' : 'PLAY'}
</button>
<div style={{ display: 'flex', gap: '2px', background: '#1e293b', padding: '2px', borderRadius: '4px' }}>
{[1, 10, 100, 1000].map(s => (
<button key={s} onClick={() => setPlaySpeed(s)} aria-label={`Vitesse ${s}x`} style={{ background: playSpeed === s ? '#3b82f6' : 'transparent', color: '#fff', border: 'none', padding: '2px 8px', fontSize: '0.7rem', cursor: 'pointer' }}>x{s}</button>
))}
</div>
<span style={{ fontFamily: 'monospace', color: '#4ade80', fontSize: '1.1rem', minWidth: '80px' }}>{formatTime(currentTime)}</span>
<input type="range" aria-label="Curseur temporel" style={{ flex: 1 }} min={currentTime - 1800} max={currentTime + 1800} value={currentTime} onChange={e => { setCurrentTime(Number(e.target.value)); setIsPlaying(false); }} />
</div>
<main style={{ flex: 1, display: 'flex', overflow: 'hidden' }}>
<div style={{ flex: 1, position: 'relative' }}>
<MapContainer center={DEFAULT_CENTER} zoom={13} style={{ height: '100%', width: '100%' }}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<NodeMarkers nodes={nodes} selectedNode={selectedNode} onSelectNode={setSelectedNode} adcValues={allAdcValues} showOnlyWithData={showOnlyWithData} />
</MapContainer>
</div>
<Sidebar selectedNode={selectedNode} dataWindow={dataWindow} loading={loading} sampleRate={sampleRate} />
</main>
<SeismicSection nodes={nodes} currentTime={currentTime} channel={selectedChannel} visible={showSection} onClose={() => setShowSection(false)} />
<div style={{ position: 'fixed', bottom: '20px', right: '20px', zIndex: 10000 }}>
{!chatOpen ? (
<button onClick={() => setChatOpen(true)} aria-label="Ouvrir le chat assistant" style={{ width: '50px', height: '50px', borderRadius: '25px', background: '#e94560', border: 'none', color: '#fff', fontSize: '20px', cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.3)' }}>💬</button>
) : (
<div style={{ background: '#1e293b', width: '300px', height: '400px', borderRadius: '8px', display: 'flex', flexDirection: 'column', border: '1px solid #334155', boxShadow: '0 8px 24px rgba(0,0,0,0.4)' }}>
<div style={{ padding: '10px', background: '#334155', display: 'flex', justifyContent: 'space-between', borderRadius: '8px 8px 0 0' }}>
<span style={{ fontSize: '0.8rem', fontWeight: 'bold' }}>Assistant</span>
<button onClick={() => setChatOpen(false)} aria-label="Fermer le chat" style={{ background: 'none', border: 'none', color: '#fff', cursor: 'pointer' }}>×</button>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }}>
{chatHistory.map((m, i) => (
<div key={i} style={{ alignSelf: m.role === 'user' ? 'flex-end' : 'flex-start', background: m.role === 'user' ? '#3b82f6' : '#475569', padding: '6px 10px', borderRadius: '8px', fontSize: '0.8rem', maxWidth: '85%' }}>{m.text}</div>
))}
</div>
<div style={{ padding: '10px', borderTop: '1px solid #334155', display: 'flex', gap: '5px' }}>
<input value={chatMsg} onChange={e => setChatMsg(e.target.value)} onKeyPress={e => e.key === 'Enter' && sendChat()} aria-label="Poser une question" style={{ flex: 1, background: '#0f172a', border: '1px solid #334155', color: '#fff', padding: '5px', borderRadius: '4px', fontSize: '0.8rem' }} placeholder="Question…" />
<button onClick={sendChat} style={{ background: '#e94560', border: 'none', color: '#fff', padding: '5px 10px', borderRadius: '4px', fontSize: '0.8rem' }}>OK</button>
</div>
</div>
)}
</div>
{showDashboard && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowDashboard(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer Dashboard</button>
<H5Dashboard />
</div>
</div>
)}
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
{showCampaignDocs && (
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: '#f1f5f9', zIndex: 9998, overflow: 'auto' }}>
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
<div style={{ position: 'relative' }}>
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
<button onClick={() => setShowDashboard(false)} style={{ position: 'fixed', top: '10px', right: '10px', background: '#ef4444', color: 'white', border: 'none', padding: '10px 20px', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', zIndex: 9999 }}>✕ Fermer Dashboard</button>
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
<H5Dashboard />
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
</div>
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
</div>
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
)}
{showCampaignDocs && (
<div style={{ position: "fixed", top: 0, left: 0, right: 0, bottom: 0, background: "#f1f5f9", zIndex: 9998, overflow: "auto" }}>
<div style={{ position: "relative" }}>
<button onClick={() => setShowCampaignDocs(false)} style={{ position: "fixed", top: "10px", right: "10px", background: "#ef4444", color: "white", border: "none", padding: "10px 20px", borderRadius: "4px", cursor: "pointer", fontWeight: "bold", zIndex: 9999 }}>✕ Fermer</button>
<CampaignDocs />
)}
</div>
</div>
)}
{showDocumentation && <DataDocumentation onClose={() => setShowDocumentation(false)} />}
{showCoverage && <H5Coverage onClose={() => setShowCoverage(false)} />}
</div>
</>
);
}
export default App;

View File

@@ -0,0 +1,118 @@
import { useEffect, useMemo } from 'react';
import { CircleMarker, Popup, useMap } from 'react-leaflet';
import proj4 from 'proj4';
import { Node, AdcValues } from '../types';
// Définition de la projection UTM Zone 31N (WGS84)
const UTM31N = "+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs";
const WGS84 = "EPSG:4326";
function utmToLatLon(easting: number, northing: number): [number, number] | null {
if (!easting || !northing || isNaN(easting) || isNaN(northing)) return null;
try {
// Proj4 attend [longitude, latitude] pour le résultat
const [lon, lat] = proj4(UTM31N, WGS84, [easting, northing]);
return [lat, lon];
} catch (e) {
console.error('UTM Conversion error:', e);
return null;
}
}
function getMarkerColor(value: number | undefined, maxVal = 500): string {
if (value === undefined || value <= 0) return '#444444';
const normalized = Math.min(1, Math.sqrt(value) / Math.sqrt(maxVal));
if (normalized < 0.3) return `rgb(0, ${Math.round(normalized * 3.3 * 255)}, 255)`;
if (normalized < 0.6) return `rgb(0, 255, ${Math.round((1 - (normalized - 0.3) * 3.3) * 255)})`;
return `rgb(255, ${Math.round((1 - (normalized - 0.6) * 2.5) * 255)}, 0)`;
}
function getMarkerRadius(value: number | undefined, maxVal = 500): number {
if (value === undefined || value <= 0) return 6;
const normalized = Math.min(1, Math.sqrt(value) / Math.sqrt(maxVal));
return 6 + normalized * 24;
}
interface NodeMarkersProps {
nodes: Node[];
selectedNode: Node | null;
onSelectNode: (node: Node) => void;
adcValues: AdcValues;
showOnlyWithData?: boolean;
}
function NodeMarkers({ nodes, selectedNode, onSelectNode, adcValues, showOnlyWithData = true }: NodeMarkersProps) {
const map = useMap();
const nodesWithLatLon = useMemo(() => {
console.log(`NodeMarkers: Processing ${nodes.length} nodes`);
const results = nodes
.filter(node => node.position && (!showOnlyWithData || node.hasDates))
.map(node => {
const coords = utmToLatLon(node.position!.easting, node.position!.northing);
if (!coords) return null;
return { ...node, lat: coords[0], lon: coords[1] };
})
.filter((n): n is Node & { lat: number; lon: number } => n !== null);
console.log(`NodeMarkers: ${results.length} nodes converted to LatLon`);
if (results.length > 0) {
console.log('Sample node:', results[0].id, results[0].lat, results[0].lon);
}
return results;
}, [nodes, showOnlyWithData]);
useEffect(() => {
if (nodesWithLatLon.length === 0) return;
try {
const lats = nodesWithLatLon.map(n => n.lat);
const lons = nodesWithLatLon.map(n => n.lon);
const bounds: [[number, number], [number, number]] = [
[Math.min(...lats), Math.min(...lons)],
[Math.max(...lats), Math.max(...lons)]
];
map.fitBounds(bounds, { padding: [50, 50] });
} catch (e) {
console.error('fitBounds error:', e);
}
}, [nodesWithLatLon, map]);
const currentMax = useMemo(() => {
const vals = Object.values(adcValues).filter(v => v > 0);
return vals.length > 0 ? Math.max(...vals) : 500;
}, [adcValues]);
return (
<>
<CircleMarker center={[43.40, 3.70]} radius={10} pathOptions={{ color: 'yellow', fillColor: 'yellow', fillOpacity: 1 }}>
<Popup>DEBUG: Centre de Sète</Popup>
</CircleMarker>
{nodesWithLatLon.map(node => {
const adcValue = adcValues[node.id];
const isSelected = selectedNode?.id === node.id;
return (
<CircleMarker
key={node.id}
center={[node.lat, node.lon]}
radius={getMarkerRadius(adcValue, currentMax)}
pathOptions={{
fillColor: getMarkerColor(adcValue, currentMax),
fillOpacity: 0.85,
color: isSelected ? '#ff0000' : '#ffffff',
weight: isSelected ? 4 : 1,
}}
eventHandlers={{ click: () => onSelectNode(node) }}
>
<Popup>
<strong>Node {node.id}</strong><br/>
E: {node.position?.easting.toFixed(0)} N: {node.position?.northing.toFixed(0)}<br/>
RMS: {adcValue?.toFixed(2) || 'N/A'}
</Popup>
</CircleMarker>
);
})}
</>
);
}
export default NodeMarkers;

View File

@@ -0,0 +1,113 @@
import { useState, useEffect, useMemo } from 'react';
import Plot from 'react-plotly.js';
interface SeismicSectionProps {
nodes: any[];
currentTime: number;
channel: string;
visible: boolean;
onClose: () => void;
}
const API_BASE = '/seismic/api';
function SeismicSection({ nodes, currentTime, channel, visible, onClose }: SeismicSectionProps) {
const [data, setData] = useState<any>(null);
const [viewMode, setViewMode] = useState<'day' | 'global'>('day');
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!visible) return;
setLoading(true);
const url = viewMode === 'global'
? `${API_BASE}/global-history?channel=${channel}`
: `${API_BASE}/rms-timeline?date=${new Date(currentTime * 1000).toISOString().split('T')[0]}&channel=${channel}`;
fetch(url)
.then(r => r.json())
.then(d => {
setData(d.nodes);
setLoading(false);
})
.catch(() => setLoading(false));
}, [visible, viewMode, channel, currentTime]);
useEffect(() => {
if (!visible) return;
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
document.addEventListener('keydown', handleEscape);
return () => document.removeEventListener('keydown', handleEscape);
}, [visible, onClose]);
const plotData = useMemo(() => {
if (!data) return [];
const traces: any[] = [];
const nodeIds = Object.keys(data).sort();
nodeIds.forEach((id, index) => {
const points = data[id];
if (!points || points.length === 0) return;
const maxRms = Math.max(...points.map((p: any) => p.rms)) || 1;
traces.push({
x: points.map((p: any) => new Date(p.ts * 1000)),
y: points.map((p: any) => (p.rms / maxRms) * 0.9 + index),
type: 'scatter',
mode: 'lines',
name: `Node ${id}`,
line: { color: '#4ade80', width: 1 },
fill: 'tonexty',
showlegend: false
});
});
return traces;
}, [data]);
if (!visible) return null;
return (
<div style={{ position: 'fixed', top: '0', left: '0', right: '0', bottom: '0', background: '#0f172a', zIndex: 3000, display: 'flex', flexDirection: 'column', overflowBehavior: 'contain' }}>
<div style={{ padding: '15px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: '#1e293b' }}>
<div>
<h2 style={{ margin: 0, display: 'inline', marginRight: '20px' }}>Vue Sismique Globale</h2>
<div style={{ display: 'inline-flex', gap: '5px', background: '#0f172a', padding: '3px', borderRadius: '4px' }}>
<button onClick={() => setViewMode('day')} style={{ background: viewMode === 'day' ? '#3b82f6' : 'transparent', color: '#fff', border: 'none', padding: '5px 15px', cursor: 'pointer' }}>Journée</button>
<button onClick={() => setViewMode('global')} style={{ background: viewMode === 'global' ? '#3b82f6' : 'transparent', color: '#fff', border: 'none', padding: '5px 15px', cursor: 'pointer' }}>Toute la période (12j)</button>
</div>
</div>
<button onClick={onClose} style={{ background: '#ef4444', color: '#fff', border: 'none', padding: '8px 20px', borderRadius: '4px', cursor: 'pointer' }}>Fermer</button>
</div>
<div style={{ flex: 1, padding: '10px' }}>
{loading ? <div style={{ textAlign: 'center', marginTop: '20%' }}>Fusion des données</div> : (
<Plot
data={plotData}
layout={{
autosize: true,
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
margin: { t: 10, b: 50, l: 60, r: 20 },
font: { color: '#94a3b8' },
xaxis: { gridcolor: '#1e293b', title: 'Temps' },
yaxis: {
gridcolor: '#1e293b',
tickvals: plotData.map((_, i) => i),
ticktext: Object.keys(data || {}).sort().map(id => `b${id}`)
}
}}
config={{ responsive: true }}
style={{ width: '100%', height: '100%' }}
/>
)}
</div>
</div>
);
}
export default SeismicSection;

127
frontend_src/Sidebar.tsx Normal file
View File

@@ -0,0 +1,127 @@
import { useState, useMemo } from 'react';
import Plot from 'react-plotly.js';
import { Node, DataWindow } from '../types';
interface SidebarProps {
selectedNode: Node | null;
dataWindow: DataWindow | null;
loading: boolean;
sampleRate: number;
}
type ViewMode = 'waveform' | 'rms';
function Sidebar({ selectedNode, dataWindow, loading, sampleRate }: SidebarProps) {
const [viewMode, setViewMode] = useState<ViewMode>('waveform');
const [showRaw, setShowRaw] = useState(false);
const samples = dataWindow?.samples || [];
const startTs = dataWindow?.startTimestamp || 0;
// Calcul RMS glissant
const rmsData = useMemo(() => {
if (samples.length === 0) return [];
const windowSize = 20;
const result = [];
for (let i = 0; i < samples.length; i++) {
const start = Math.max(0, i - windowSize);
const window = samples.slice(start, i + 1);
const rms = Math.sqrt(window.reduce((a, b) => a + b*b, 0) / window.length);
result.push(rms);
}
return result;
}, [samples]);
// Formatter pour le X (DD hh:mm:ss)
const formatXAxis = (ts: number) => {
const d = new Date(ts * 1000);
const day = d.getDate().toString().padStart(2, '0');
const h = d.getHours().toString().padStart(2, '0');
const m = d.getMinutes().toString().padStart(2, '0');
const s = d.getSeconds().toString().padStart(2, '0');
return `${day} ${h}:${m}:${s}`;
};
const xValues = useMemo(() => {
return samples.map((_, i) => formatXAxis(startTs + i / sampleRate));
}, [samples, startTs, sampleRate]);
const plotData: any[] = [];
if (samples.length > 0) {
if (viewMode === 'waveform') {
plotData.push({
x: xValues,
y: samples,
type: 'scatter',
mode: 'lines',
name: 'ADC Brute',
line: { color: '#4ade80', width: 1.5 }
});
} else {
plotData.push({
x: xValues,
y: rmsData,
type: 'scatter',
mode: 'lines',
name: 'RMS',
line: { color: '#fbbf24', width: 2 }
});
}
}
return (
<aside className="sidebar" style={{ width: '450px', background: '#1e293b', borderLeft: '1px solid #334155', display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ padding: '20px', borderBottom: '1px solid #334155' }}>
<h2 style={{ margin: 0, color: '#f8fafc' }}>Analyse Node</h2>
</div>
<div style={{ flex: 1, overflowY: 'auto', padding: '20px' }}>
{selectedNode ? (
<>
<div style={{ marginBottom: '20px', background: '#0f172a', padding: '15px', borderRadius: '8px' }}>
<h3 style={{ margin: '0 0 10px 0', color: '#38bdf8' }}>Node b{selectedNode.id}</h3>
<p style={{ margin: '5px 0', fontSize: '0.9rem' }}>E: {selectedNode.position?.easting.toFixed(0)} N: {selectedNode.position?.northing.toFixed(0)}</p>
</div>
<div style={{ display: 'flex', gap: '5px', marginBottom: '15px' }}>
<button onClick={() => setViewMode('waveform')} style={{ flex: 1, padding: '8px', background: viewMode === 'waveform' ? '#3b82f6' : '#334155', border: 'none', color: '#fff', borderRadius: '4px', cursor: 'pointer' }}>Waveform (Brute)</button>
<button onClick={() => setViewMode('rms')} style={{ flex: 1, padding: '8px', background: viewMode === 'rms' ? '#3b82f6' : '#334155', border: 'none', color: '#fff', borderRadius: '4px', cursor: 'pointer' }}>RMS</button>
</div>
{loading ? (
<div style={{ textAlign: 'center', padding: '40px', color: '#94a3b8' }}>Chargement</div>
) : samples.length > 0 ? (
<>
<div style={{ background: '#000', borderRadius: '8px', overflow: 'hidden', height: '300px' }}>
<Plot
data={plotData}
layout={{
autosize: true,
height: 300,
margin: { t: 10, b: 60, l: 50, r: 10 },
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
font: { color: '#94a3b8', size: 10 },
xaxis: {
gridcolor: '#1e293b',
zerolinecolor: '#334155',
tickangle: -45,
nticks: 5
},
yaxis: { gridcolor: '#1e293b', zerolinecolor: '#334155', title: 'Amplitude' }
}}
config={{ responsive: true, displayModeBar: true }}
style={{ width: '100%' }}
/>
</div>
{/* Stats... */}
</>
) : null}
</>
) : null}
</div>
</aside>
);
}
export default Sidebar;

View File

@@ -0,0 +1,241 @@
import React, { useState, useEffect } from 'react';
interface TimelineEvent {
date: string;
event: string;
type: 'start' | 'data' | 'milestone' | 'operation' | 'end';
}
interface Document {
name: string;
file: string;
category: string;
}
interface CampaignManifest {
timeline: TimelineEvent[];
documents: Document[];
}
export default function CampaignDocs() {
const [manifest, setManifest] = useState<CampaignManifest | null>(null);
const [activeTab, setActiveTab] = useState<'timeline' | 'docs'>('timeline');
useEffect(() => {
fetch('/seismic/api/docs/manifest')
.then(r => r.json())
.then(setManifest)
.catch(console.error);
}, []);
if (!manifest) return <div style={{ padding: '20px', color: '#64748b' }}>Chargement...</div>;
const typeColors: Record<TimelineEvent['type'], string> = {
start: '#10b981',
data: '#3b82f6',
milestone: '#f59e0b',
operation: '#8b5cf6',
end: '#ef4444'
};
const typeIcons: Record<TimelineEvent['type'], string> = {
start: '🚀',
data: '📊',
milestone: '🎯',
operation: '⚙️',
end: '🏁'
};
return (
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
{/* Header */}
<div style={{ marginBottom: '30px' }}>
<h1 style={{ fontSize: '2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '10px' }}>
📚 Campagne SeaKESP - Sète 2020
</h1>
<p style={{ color: '#64748b', fontSize: '1.1rem' }}>
Documentation, chronologie et ressources de la campagne OBN
</p>
</div>
{/* Tabs */}
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px', borderBottom: '2px solid #e2e8f0' }}>
<button
onClick={() => setActiveTab('timeline')}
style={{
padding: '10px 20px',
background: activeTab === 'timeline' ? '#3b82f6' : 'transparent',
color: activeTab === 'timeline' ? 'white' : '#64748b',
border: 'none',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '1rem'
}}
>
📅 Chronologie
</button>
<button
onClick={() => setActiveTab('docs')}
style={{
padding: '10px 20px',
background: activeTab === 'docs' ? '#3b82f6' : 'transparent',
color: activeTab === 'docs' ? 'white' : '#64748b',
border: 'none',
borderRadius: '8px 8px 0 0',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '1rem'
}}
>
📄 Documents
</button>
</div>
{/* Timeline Tab */}
{activeTab === 'timeline' && (
<div style={{ background: 'white', padding: '30px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '20px' }}>
Chronologie de la campagne
</h2>
<div style={{ position: 'relative', paddingLeft: '40px' }}>
{/* Timeline line */}
<div style={{
position: 'absolute',
left: '15px',
top: '10px',
bottom: '10px',
width: '3px',
background: 'linear-gradient(to bottom, #10b981, #ef4444)'
}} />
{manifest.timeline.map((event, idx) => (
<div key={idx} style={{ position: 'relative', marginBottom: '25px' }}>
{/* Timeline dot */}
<div style={{
position: 'absolute',
left: '-30px',
width: '15px',
height: '15px',
borderRadius: '50%',
background: typeColors[event.type],
border: '3px solid white',
boxShadow: '0 2px 8px rgba(0,0,0,0.2)'
}} />
{/* Event card */}
<div style={{
background: '#f8fafc',
padding: '15px',
borderRadius: '8px',
border: `2px solid ${typeColors[event.type]}`,
marginLeft: '10px'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '5px' }}>
<span style={{ fontSize: '1.5rem' }}>{typeIcons[event.type]}</span>
<span style={{ fontSize: '0.9rem', fontWeight: 'bold', color: typeColors[event.type] }}>
{event.date}
</span>
</div>
<p style={{ fontSize: '1rem', color: '#334155', margin: 0 }}>
{event.event}
</p>
</div>
</div>
))}
</div>
{/* Stats */}
<div style={{ marginTop: '30px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px' }}>
<div style={{ background: '#f0f9ff', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#3b82f6' }}>46 jours</div>
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>Durée totale</div>
</div>
<div style={{ background: '#f0fdf4', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#10b981' }}>345 fichiers</div>
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>RAW collectés</div>
</div>
<div style={{ background: '#fef3c7', padding: '15px', borderRadius: '8px', textAlign: 'center' }}>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#f59e0b' }}>~800h</div>
<div style={{ color: '#64748b', fontSize: '0.9rem' }}>Enregistrements</div>
</div>
</div>
</div>
)}
{/* Documents Tab */}
{activeTab === 'docs' && (
<div style={{ background: 'white', padding: '30px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '20px' }}>
Documents de référence
</h2>
{['Sources', 'Acquisition', 'Specifications', 'Geometry', 'Maps'].map(category => {
const categoryDocs = manifest.documents.filter(d => d.category === category);
if (categoryDocs.length === 0) return null;
const categoryIcons: Record<string, string> = {
Sources: '🎺',
Acquisition: '📋',
Specifications: '📖',
Geometry: '🗺️',
Maps: '🗺️'
};
return (
<div key={category} style={{ marginBottom: '25px' }}>
<h3 style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#475569', marginBottom: '10px', display: 'flex', alignItems: 'center', gap: '10px' }}>
<span>{categoryIcons[category]}</span>
{category}
</h3>
<div style={{ display: 'grid', gap: '10px' }}>
{categoryDocs.map((doc, idx) => (
<a
key={idx}
href={`/seismic/api/docs/${doc.file}`}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'flex',
alignItems: 'center',
gap: '15px',
padding: '15px',
background: '#f8fafc',
border: '1px solid #e2e8f0',
borderRadius: '8px',
textDecoration: 'none',
color: '#334155',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = '#f1f5f9';
e.currentTarget.style.borderColor = '#3b82f6';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = '#f8fafc';
e.currentTarget.style.borderColor = '#e2e8f0';
}}
>
<div style={{ fontSize: '1.5rem' }}>
{doc.file.endsWith('.pdf') ? '📄' :
doc.file.endsWith('.xlsx') || doc.file.endsWith('.xlsm') ? '📊' :
doc.file.endsWith('.docx') ? '📝' :
doc.file.endsWith('.png') ? '🖼️' : '📁'}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 'bold', marginBottom: '3px' }}>{doc.name}</div>
<div style={{ fontSize: '0.85rem', color: '#64748b' }}>{doc.file}</div>
</div>
<div style={{ color: '#3b82f6', fontSize: '1.2rem' }}></div>
</a>
))}
</div>
</div>
);
})}
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,206 @@
import React, { useState } from 'react';
interface Props {
onClose: () => void;
}
export default function DataDocumentation({ onClose }: Props) {
const [activeTab, setActiveTab] = useState<'overview' | 'channels' | 'pipeline' | 'conversion' | 'format'>('overview');
const tabs = [
{ id: 'overview' as const, label: 'Vue d\'ensemble', icon: '📋' },
{ id: 'channels' as const, label: 'Canaux', icon: '📊' },
{ id: 'pipeline' as const, label: 'Pipeline', icon: '🔄' },
{ id: 'conversion' as const, label: 'Conversion', icon: '⚙️' },
{ id: 'format' as const, label: 'Format H5', icon: '📦' }
];
return (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', zIndex: 10000, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '20px' }}>
<div style={{ background: 'white', borderRadius: '12px', maxWidth: '900px', width: '100%', maxHeight: '90vh', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
{/* Header */}
<div style={{ padding: '20px', borderBottom: '2px solid #e2e8f0', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 style={{ fontSize: '1.5rem', fontWeight: 'bold', color: '#1e293b', margin: 0 }}>
Documentation Technique
</h2>
<button onClick={onClose} style={{ background: '#ef4444', color: 'white', border: 'none', padding: '8px 16px', borderRadius: '6px', cursor: 'pointer', fontWeight: 'bold' }}>
Fermer
</button>
</div>
{/* Tabs */}
<div style={{ display: 'flex', borderBottom: '2px solid #e2e8f0', overflowX: 'auto' }}>
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
padding: '12px 20px',
background: activeTab === tab.id ? '#3b82f6' : 'transparent',
color: activeTab === tab.id ? 'white' : '#64748b',
border: 'none',
borderBottom: activeTab === tab.id ? '3px solid #2563eb' : '3px solid transparent',
cursor: 'pointer',
fontWeight: activeTab === tab.id ? 'bold' : 'normal',
fontSize: '0.9rem',
whiteSpace: 'nowrap'
}}
>
{tab.icon} {tab.label}
</button>
))}
</div>
{/* Content */}
<div style={{ flex: 1, overflowY: 'auto', padding: '30px' }}>
{activeTab === 'overview' && (
<div>
<h3 style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>Campagne SeaKESP - Sète 2020</h3>
<p style={{ fontSize: '1rem', color: '#475569', marginBottom: '20px' }}>
Campagne d'acquisition sismique Ocean Bottom Node (OBN) réalisée en août-septembre 2020.
</p>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginTop: '20px', marginBottom: '10px' }}>Données acquises</h4>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li><strong>345 fichiers RAW</strong> propriétaires (2.7 TB)</li>
<li><strong>Durée totale :</strong> ~800 heures d'enregistrements continus</li>
<li><strong>Plus long fichier :</strong> 60 heures (217k secondes)</li>
<li><strong>Fréquence d'échantillonnage :</strong> 500 Hz</li>
</ul>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginTop: '20px', marginBottom: '10px' }}>État de conversion</h4>
<p style={{ color: '#475569' }}>
Conversion RAW → H5 en cours sur VM .81 (4 workers parallèles).
</p>
</div>
)}
{activeTab === 'channels' && (
<div>
<h3 style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>Canaux d'acquisition</h3>
<div style={{ marginBottom: '25px' }}>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#0369a1', marginBottom: '10px' }}>Géophones (Canaux 1-3)</h4>
<p style={{ color: '#475569', marginBottom: '10px' }}>Capteurs de vitesse particulaire 3 composantes (X, Y, Z)</p>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li><strong>Type :</strong> Géophones 15.6 V/(m/s)</li>
<li><strong>Unité finale :</strong> m/s (mètres par seconde)</li>
<li><strong>Sensibilité ADC :</strong> 3.576e-7 V/bit</li>
<li><strong>Calibration :</strong> m/s = (ADC × 3.576e-7) / 15.6</li>
</ul>
</div>
<div>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#7c2d12', marginBottom: '10px' }}>Hydrophone (Canal 4)</h4>
<p style={{ color: '#475569', marginBottom: '10px' }}>Capteur de pression acoustique</p>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li><strong>Type :</strong> Hydrophone 8.9 V/bar</li>
<li><strong>Unité finale :</strong> Pa (Pascals)</li>
<li><strong>Sensibilité ADC :</strong> 2.841e-6 V/bit</li>
<li><strong>Calibration :</strong> Pa = (ADC × 2.841e-6 / 8.9) × 100000</li>
<li><strong>Note :</strong> 1 bar = 100000 Pa</li>
</ul>
</div>
</div>
)}
{activeTab === 'pipeline' && (
<div>
<h3 style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>Pipeline de traitement</h3>
<div style={{ background: '#f8fafc', padding: '20px', borderRadius: '8px', marginBottom: '20px' }}>
<h4 style={{ fontSize: '1rem', fontWeight: 'bold', color: '#475569', marginBottom: '15px' }}>Étapes de conversion</h4>
<ol style={{ color: '#475569', lineHeight: '2', paddingLeft: '20px' }}>
<li><strong>Fichier source :</strong> RAW propriétaire (format binaire non documenté)</li>
<li><strong>Conversion initiale :</strong> RAW SEGY via MantaSegy</li>
<li><strong>Parsing SEGY :</strong> Extraction headers + données ADC brutes</li>
<li><strong>Calibration :</strong> Application formules ADC unités physiques</li>
<li><strong>Export H5 :</strong> Sauvegarde données brutes + calibrées</li>
<li><strong>Validation :</strong> Vérification précision (erreur &lt; 10¹¹)</li>
</ol>
</div>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginTop: '20px', marginBottom: '10px' }}>Objectif du traitement</h4>
<p style={{ color: '#475569' }}>Convertir les fichiers RAW propriétaires en format HDF5 standardisé avec :</p>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li>Données ADC brutes (int32) pour archivage</li>
<li>Données calibrées en unités physiques (float32)</li>
<li>Métadonnées complètes (sample rate, durée, calibration)</li>
</ul>
</div>
)}
{activeTab === 'conversion' && (
<div>
<h3 style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>Détails de conversion</h3>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginBottom: '10px' }}>Configuration actuelle</h4>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li><strong>Machine :</strong> VM .81 (osboxes@192.168.0.81)</li>
<li><strong>Workers :</strong> 4 processus parallèles</li>
<li><strong>Stockage source :</strong> NFS depuis Pi 52 (/mnt/seismic-data)</li>
<li><strong>Stockage destination :</strong> Local VM puis rsync vers Pi 27</li>
</ul>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginTop: '20px', marginBottom: '10px' }}>Formules de calibration</h4>
<div style={{ background: '#f1f5f9', padding: '15px', borderRadius: '8px', fontFamily: 'monospace', fontSize: '0.9rem' }}>
<p style={{ color: '#0369a1', marginBottom: '10px' }}><strong>Géophones (canaux 1-3) :</strong></p>
<p style={{ color: '#475569' }}>m/s = (ADC_value × 3.576e-7 V/bit) / (15.6 V/(m/s))</p>
<p style={{ color: '#7c2d12', marginTop: '15px', marginBottom: '10px' }}><strong>Hydrophone (canal 4) :</strong></p>
<p style={{ color: '#475569' }}>Pa = (ADC_value × 2.841e-6 V/bit / 8.9 V/bar) × 100000 Pa/bar</p>
</div>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginTop: '20px', marginBottom: '10px' }}>Validation qualité</h4>
<p style={{ color: '#475569' }}>
Précision de conversion vérifiée : erreur &lt; 10¹¹ entre calcul Python et valeurs attendues.
</p>
</div>
)}
{activeTab === 'format' && (
<div>
<h3 style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>Structure fichier H5</h3>
<div style={{ background: '#0f172a', padding: '20px', borderRadius: '8px', color: '#e2e8f0', fontFamily: 'monospace', fontSize: '0.85rem', marginBottom: '20px' }}>
<pre style={{ margin: 0 }}>{`/
├── metadata (group)
│ ├── @duration_sec: 20.0
│ ├── @sample_rate_hz: 500
│ ├── @n_channels: 4
│ └── @n_samples: 10000
├── calibration (group)
│ ├── @geophone_v_per_bit: 3.576e-7
│ ├── @geophone_v_per_ms: 15.6
│ ├── @hydrophone_v_per_bit: 2.841e-6
│ └── @hydrophone_v_per_bar: 8.9
├── raw_data (group)
│ ├── channel_1 (dataset int32)
│ ├── channel_2 (dataset int32)
│ ├── channel_3 (dataset int32)
│ └── channel_4 (dataset int32)
└── calibrated_data (group)
├── channel_1 (dataset float32, unit: m/s)
├── channel_2 (dataset float32, unit: m/s)
├── channel_3 (dataset float32, unit: m/s)
└── channel_4 (dataset float32, unit: Pa)`}</pre>
</div>
<h4 style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#334155', marginBottom: '10px' }}>Avantages du format H5</h4>
<ul style={{ color: '#475569', lineHeight: '1.8' }}>
<li><strong>Compression efficace :</strong> Réduction ~30% taille fichiers</li>
<li><strong>Accès rapide :</strong> Lecture sélective par canal/plage temporelle</li>
<li><strong>Métadonnées intégrées :</strong> Toutes les infos de calibration dans le fichier</li>
<li><strong>Interopérabilité :</strong> Compatible Python, MATLAB, Julia, C++</li>
<li><strong>Archivage :</strong> Données brutes + calibrées dans un seul fichier</li>
</ul>
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,151 @@
import { useState, useEffect } from 'react';
const API_BASE = '/seismic/api';
interface CoverageStats {
total_positions: number;
with_data: number;
with_aux: number;
total_files: number;
coverage_pct: number;
missing: number;
}
interface Gap {
start: number;
end: number;
length: number;
}
function H5Coverage({ onClose }: { onClose: () => void }) {
const [stats, setStats] = useState<CoverageStats | null>(null);
const [gaps, setGaps] = useState<Gap[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
Promise.all([
fetch(`${API_BASE}/h5/coverage`).then(r => r.json()),
fetch(`${API_BASE}/h5/gaps`).then(r => r.json())
]).then(([statsData, gapsData]) => {
setStats(statsData);
setGaps(gapsData.gaps.sort((a: Gap, b: Gap) => b.length - a.length));
setLoading(false);
});
}, []);
if (loading) {
return (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: '#0f172a', zIndex: 3000, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
<div>Chargement des statistiques H5</div>
</div>
);
}
return (
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: '#0f172a', zIndex: 3000, display: 'flex', flexDirection: 'column', overflowY: 'auto' }}>
<div style={{ padding: '20px', background: '#1e293b', borderBottom: '1px solid #334155', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h2 style={{ margin: 0, color: '#f8fafc' }}>📊 H5 Data Coverage</h2>
<button onClick={onClose} style={{ background: '#ef4444', color: '#fff', border: 'none', padding: '8px 20px', borderRadius: '4px', cursor: 'pointer' }}>Fermer</button>
</div>
<div style={{ flex: 1, padding: '20px', maxWidth: '1200px', margin: '0 auto', width: '100%' }}>
{/* Stats globales */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '15px', marginBottom: '30px' }}>
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Positions totales</div>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#f8fafc' }}>{stats?.total_positions}</div>
</div>
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Avec données</div>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#4ade80' }}>{stats?.with_data}</div>
</div>
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Manquantes</div>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#ef4444' }}>{stats?.missing}</div>
</div>
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Coverage</div>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: stats && stats.coverage_pct > 50 ? '#4ade80' : '#fbbf24' }}>{stats?.coverage_pct}%</div>
</div>
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<div style={{ fontSize: '0.8rem', color: '#94a3b8', marginBottom: '5px' }}>Fichiers totaux</div>
<div style={{ fontSize: '2rem', fontWeight: 'bold', color: '#38bdf8' }}>{stats?.total_files}</div>
</div>
</div>
{/* Progress bar */}
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155', marginBottom: '30px' }}>
<div style={{ fontSize: '0.9rem', color: '#94a3b8', marginBottom: '10px' }}>Progression du déploiement</div>
<div style={{ background: '#0f172a', height: '30px', borderRadius: '15px', overflow: 'hidden', position: 'relative' }}>
<div style={{
background: 'linear-gradient(90deg, #4ade80, #22c55e)',
height: '100%',
width: `${stats?.coverage_pct}%`,
transition: 'width 1s ease'
}}></div>
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', color: '#fff', fontWeight: 'bold', fontSize: '0.9rem' }}>
{stats?.with_data} / {stats?.total_positions} positions
</div>
</div>
</div>
{/* Top 10 gaps */}
<div style={{ background: '#1e293b', padding: '20px', borderRadius: '8px', border: '1px solid #334155' }}>
<h3 style={{ margin: '0 0 15px 0', color: '#f8fafc' }}>🔍 Top 10 Gaps (plages manquantes)</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{gaps.slice(0, 10).map((gap, i) => (
<div key={i} style={{ background: '#0f172a', padding: '12px', borderRadius: '6px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<span style={{ color: '#f8fafc', fontWeight: 'bold' }}>b{gap.start}</span>
<span style={{ color: '#94a3b8', margin: '0 8px' }}></span>
<span style={{ color: '#f8fafc', fontWeight: 'bold' }}>b{gap.end}</span>
</div>
<div style={{
background: gap.length > 50 ? '#dc2626' : gap.length > 20 ? '#f59e0b' : '#3b82f6',
color: '#fff',
padding: '4px 12px',
borderRadius: '12px',
fontSize: '0.85rem',
fontWeight: 'bold'
}}>
{gap.length} positions
</div>
</div>
))}
</div>
{gaps.length > 10 && (
<div style={{ marginTop: '15px', textAlign: 'center', color: '#94a3b8', fontSize: '0.85rem' }}>
et {gaps.length - 10} autres gaps
</div>
)}
</div>
{/* Verdict */}
<div style={{
background: stats && stats.coverage_pct < 50 ? '#7f1d1d' : '#065f46',
padding: '20px',
borderRadius: '8px',
marginTop: '30px',
border: stats && stats.coverage_pct < 50 ? '1px solid #dc2626' : '1px solid #10b981'
}}>
<div style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#fff', marginBottom: '10px' }}>
{stats && stats.coverage_pct < 50 ? '⚠️ Déploiement partiel détecté' : '✅ Coverage acceptable'}
</div>
<div style={{ color: '#e5e7eb', fontSize: '0.9rem' }}>
{stats && stats.coverage_pct < 50
? `Seulement ${stats.coverage_pct}% des positions planifiées ont des données. Cela suggère un test de déploiement plutôt qu'une collecte complète.`
: `${stats?.coverage_pct}% des positions déployées avec succès.`
}
</div>
</div>
</div>
</div>
);
}
export default H5Coverage;

View File

@@ -0,0 +1,246 @@
import React, { useState, useEffect } from 'react';
interface H5File {
filename: string;
nodeId: string;
date: string;
}
interface WaveformData {
samples: number[];
sample_rate: number;
duration_sec: number;
channel: number;
channel_name: string;
unit: string;
stats: {
min: number;
max: number;
mean: number;
std: number;
rms: number;
};
}
export default function H5Dashboard() {
const [files, setFiles] = useState<H5File[]>([]);
const [selectedFile, setSelectedFile] = useState<string | null>(null);
const [waveformData, setWaveformData] = useState<WaveformData | null>(null);
const [channel, setChannel] = useState(1);
const [loading, setLoading] = useState(false);
const [startTime, setStartTime] = useState(0);
const [duration, setDuration] = useState(10);
const [globalView, setGlobalView] = useState(false);
useEffect(() => {
fetch('/seismic/api/h5/files')
.then(r => r.json())
.then(data => setFiles(data.files || []))
.catch(console.error);
}, []);
const loadWaveform = async (file: string, ch: number, start: number, dur: number, global: boolean = false) => {
setLoading(true);
try {
const params = new URLSearchParams({
file,
channel: String(ch),
start: String(start),
duration: global ? '0' : String(dur)
});
const response = await fetch(`/seismic/api/h5/data?${params}`);
const data = await response.json();
setWaveformData(data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
const handleFileSelect = (file: string) => {
setSelectedFile(file);
setGlobalView(false);
loadWaveform(file, channel, startTime, duration, false);
};
const handleChannelChange = (ch: number) => {
setChannel(ch);
if (selectedFile) loadWaveform(selectedFile, ch, startTime, duration, globalView);
};
const handleGlobalView = () => {
if (selectedFile) {
setGlobalView(true);
loadWaveform(selectedFile, channel, 0, 0, true);
}
};
return (
<div style={{ padding: '20px', maxWidth: '1400px', margin: '0 auto' }}>
{/* Header */}
<div style={{ marginBottom: '30px' }}>
<h1 style={{ fontSize: '2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '10px' }}>
🎯 Dashboard H5 - Visualisation Waveforms
</h1>
<p style={{ color: '#64748b', fontSize: '1.1rem' }}>
{files.length} fichiers H5 calibrés disponibles
</p>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '300px 1fr', gap: '20px' }}>
{/* Sidebar - Liste des fichiers */}
<div style={{ background: 'white', padding: '20px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)', maxHeight: '80vh', overflowY: 'auto' }}>
<h2 style={{ fontSize: '1.2rem', fontWeight: 'bold', color: '#1e293b', marginBottom: '15px' }}>
📁 Fichiers H5
</h2>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
{files.map((file, idx) => (
<button
key={idx}
onClick={() => handleFileSelect(file.filename)}
style={{
textAlign: 'left',
padding: '12px',
background: selectedFile === file.filename ? '#3b82f6' : '#f8fafc',
color: selectedFile === file.filename ? 'white' : '#334155',
border: selectedFile === file.filename ? '2px solid #2563eb' : '1px solid #e2e8f0',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '0.85rem',
transition: 'all 0.2s'
}}
onMouseEnter={(e) => {
if (selectedFile !== file.filename) {
e.currentTarget.style.background = '#f1f5f9';
}
}}
onMouseLeave={(e) => {
if (selectedFile !== file.filename) {
e.currentTarget.style.background = '#f8fafc';
}
}}
>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>Node {file.nodeId}</div>
<div style={{ fontSize: '0.75rem', opacity: 0.8 }}>{file.date}</div>
</button>
))}
</div>
</div>
{/* Main content - Waveform */}
<div style={{ background: 'white', padding: '20px', borderRadius: '12px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
{!selectedFile && (
<div style={{ textAlign: 'center', padding: '60px', color: '#64748b' }}>
<div style={{ fontSize: '3rem', marginBottom: '20px' }}>📊</div>
<p style={{ fontSize: '1.2rem' }}>Sélectionnez un fichier H5 pour visualiser les waveforms</p>
</div>
)}
{selectedFile && (
<>
{/* Controls */}
<div style={{ marginBottom: '20px', display: 'flex', gap: '15px', flexWrap: 'wrap', alignItems: 'center' }}>
<div>
<label style={{ fontSize: '0.9rem', fontWeight: 'bold', color: '#475569', display: 'block', marginBottom: '5px' }}>
Canal :
</label>
<div style={{ display: 'flex', gap: '5px' }}>
{[1, 2, 3, 4].map(ch => (
<button
key={ch}
onClick={() => handleChannelChange(ch)}
style={{
padding: '8px 12px',
background: channel === ch ? '#3b82f6' : '#f1f5f9',
color: channel === ch ? 'white' : '#334155',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
{ch <= 3 ? `Geo ${ch}` : 'Hydro'}
</button>
))}
</div>
</div>
<button
onClick={handleGlobalView}
disabled={loading}
style={{
padding: '8px 16px',
background: globalView ? '#10b981' : '#8b5cf6',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: loading ? 'wait' : 'pointer',
fontWeight: 'bold'
}}
>
🌍 Vue Globale
</button>
</div>
{/* Waveform Display */}
{loading && (
<div style={{ textAlign: 'center', padding: '40px', color: '#64748b' }}>
<div style={{ fontSize: '2rem', marginBottom: '10px' }}></div>
<p>Chargement des données...</p>
</div>
)}
{waveformData && !loading && (
<div>
{/* Info bar */}
<div style={{ background: '#f8fafc', padding: '15px', borderRadius: '8px', marginBottom: '20px', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '10px' }}>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Canal</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.channel_name}</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Unité</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.unit}</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Durée</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.duration_sec.toFixed(1)} s</div>
</div>
<div>
<div style={{ fontSize: '0.75rem', color: '#64748b', marginBottom: '3px' }}>Échantillons</div>
<div style={{ fontWeight: 'bold', color: '#1e293b' }}>{waveformData.samples.length}</div>
</div>
</div>
{/* Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(100px, 1fr))', gap: '10px', marginBottom: '20px' }}>
{Object.entries(waveformData.stats).map(([key, value]) => (
<div key={key} style={{ background: '#f0f9ff', padding: '10px', borderRadius: '6px', textAlign: 'center' }}>
<div style={{ fontSize: '0.75rem', color: '#0369a1', marginBottom: '3px', textTransform: 'uppercase' }}>{key}</div>
<div style={{ fontWeight: 'bold', color: '#0c4a6e', fontSize: '0.9rem' }}>{value.toExponential(3)}</div>
</div>
))}
</div>
{/* Waveform visualization (simple text representation for now) */}
<div style={{ background: '#0f172a', padding: '20px', borderRadius: '8px', color: '#94a3b8', fontFamily: 'monospace', fontSize: '0.75rem', overflowX: 'auto' }}>
<div style={{ marginBottom: '10px', color: '#f1f5f9', fontWeight: 'bold' }}>
Waveform Preview (premiers échantillons) :
</div>
<pre style={{ margin: 0 }}>
{waveformData.samples.slice(0, 50).map((v, i) =>
`[${i.toString().padStart(4, '0')}] ${v.toExponential(4)} ${waveformData.unit}`
).join('\n')}
{waveformData.samples.length > 50 && `\n... (${waveformData.samples.length - 50} échantillons restants)`}
</pre>
</div>
</div>
)}
</>
)}
</div>
</div>
</div>
);
}

325
h5_api_server.py Normal file
View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python3
"""
Flask API server for SeaKESP H5 data and node metadata
Provides both H5 data access and node/date endpoints
"""
from flask import Flask, jsonify, request, send_file
from flask_cors import CORS
import h5py
import numpy as np
import os
import json
from pathlib import Path
from datetime import datetime
app = Flask(__name__)
CORS(app)
# Paths
DATA_DIR = Path("/data")
H5_DIR = DATA_DIR / "h5"
DOCS_DIR = DATA_DIR / "docs"
INDEX_FILE = DATA_DIR / "index.json"
# Load node index
nodes_data = {}
dates_list = []
def load_index():
"""Load node data from index.json"""
global nodes_data, dates_list
if not INDEX_FILE.exists():
print(f"Warning: {INDEX_FILE} not found")
return
try:
with open(INDEX_FILE, 'r') as f:
data = json.load(f)
nodes_data = data.get('nodes', {})
dates_list = data.get('dates', [])
print(f"Loaded {len(nodes_data)} nodes and {len(dates_list)} dates from index.json")
except Exception as e:
print(f"Error loading index.json: {e}")
# Load index on startup
load_index()
# ============================================================================
# Node & Date Endpoints (replacing Node.js backend)
# ============================================================================
@app.route('/api/nodes', methods=['GET'])
def get_nodes():
"""Return list of all nodes with metadata"""
nodes_list = []
for node_id, node_info in nodes_data.items():
nodes_list.append({
'id': node_id,
'position': node_info.get('position', {}),
'file_count': len(node_info.get('files', []))
})
return jsonify(nodes_list)
@app.route('/api/dates', methods=['GET'])
def get_dates():
"""Return list of available dates"""
return jsonify(dates_list)
@app.route('/api/migration-status', methods=['GET'])
def migration_status():
"""Return H5 migration status summary"""
h5_count = len(list(H5_DIR.glob("*.h5"))) if H5_DIR.exists() else 0
return jsonify({
'total_h5_files': h5_count,
'nodes_count': len(nodes_data),
'dates_count': len(dates_list),
'last_updated': datetime.now().isoformat()
})
@app.route('/api/rms-timeline', methods=['GET'])
def rms_timeline():
"""Placeholder for RMS timeline (requires processing)"""
return jsonify([])
@app.route('/api/global-history', methods=['GET'])
def global_history():
"""Placeholder for global history (requires processing)"""
return jsonify([])
# ============================================================================
# H5 Data Endpoints
# ============================================================================
@app.route('/api/h5/files', methods=['GET'])
def list_h5_files():
"""List all H5 files with metadata"""
if not H5_DIR.exists():
return jsonify([])
files = []
for h5_file in sorted(H5_DIR.glob("*.h5")):
try:
with h5py.File(h5_file, 'r') as f:
metadata = dict(f['metadata'].attrs)
# Extract node ID and date from filename
# Format: node_NODEID_YYYYMMDD.h5
parts = h5_file.stem.split('_')
node_id = parts[1] if len(parts) > 1 else "unknown"
date_str = parts[2] if len(parts) > 2 else "unknown"
files.append({
'filename': h5_file.name,
'path': str(h5_file),
'nodeId': node_id,
'date': date_str,
'duration_sec': float(metadata.get('duration_sec', 0)),
'sample_rate_hz': int(metadata.get('sample_rate_hz', 500)),
'n_channels': int(metadata.get('n_channels', 4)),
'n_samples': int(metadata.get('n_samples', 0))
})
except Exception as e:
print(f"Error reading {h5_file}: {e}")
return jsonify(files)
@app.route('/api/h5/data', methods=['GET'])
def get_h5_data():
"""Get waveform data from H5 file"""
filename = request.args.get('file')
channel = request.args.get('channel', 'channel_1')
start = float(request.args.get('start', 0))
duration = float(request.args.get('duration', 10))
if not filename:
return jsonify({'error': 'Missing file parameter'}), 400
h5_path = DATA_DIR / filename
if not h5_path.exists():
return jsonify({'error': 'File not found'}), 404
try:
with h5py.File(h5_path, 'r') as f:
# Get metadata
sample_rate = int(f['metadata'].attrs['sample_rate_hz'])
# Calculate sample range
start_sample = int(start * sample_rate)
n_samples = int(duration * sample_rate)
end_sample = start_sample + n_samples
# Read calibrated data
data = f['calibrated_data'][channel][start_sample:end_sample]
# Calculate statistics
stats = {
'mean': float(np.mean(data)),
'std': float(np.std(data)),
'min': float(np.min(data)),
'max': float(np.max(data)),
'rms': float(np.sqrt(np.mean(data**2)))
}
# Create time array
time = np.arange(len(data)) / sample_rate + start
return jsonify({
'time': time.tolist(),
'data': data.tolist(),
'stats': stats,
'sample_rate': sample_rate,
'channel': channel,
'start': start,
'duration': duration,
'n_samples': len(data)
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/h5/coverage', methods=['GET'])
def h5_coverage():
"""Get H5 coverage summary by node and date"""
if not H5_DIR.exists():
return jsonify([])
coverage = {}
for h5_file in H5_DIR.glob("*.h5"):
try:
parts = h5_file.stem.split('_')
node_id = parts[1] if len(parts) > 1 else "unknown"
date_str = parts[2] if len(parts) > 2 else "unknown"
key = f"{node_id}_{date_str}"
with h5py.File(h5_file, 'r') as f:
duration = float(f['metadata'].attrs.get('duration_sec', 0))
if key not in coverage:
coverage[key] = {
'nodeId': node_id,
'date': date_str,
'total_duration_hours': 0,
'file_count': 0
}
coverage[key]['total_duration_hours'] += duration / 3600
coverage[key]['file_count'] += 1
except Exception as e:
print(f"Error processing {h5_file}: {e}")
return jsonify(list(coverage.values()))
@app.route('/api/h5/gaps', methods=['GET'])
def h5_gaps():
"""Identify gaps in H5 data coverage"""
# TODO: Implement gap detection logic
return jsonify([])
# ============================================================================
# Campaign Documentation Endpoints
# ============================================================================
@app.route('/api/docs/manifest', methods=['GET'])
def get_docs_manifest():
"""Get campaign documentation manifest"""
manifest_path = DOCS_DIR / "campaign_manifest.json"
if not manifest_path.exists():
return jsonify({'error': 'Manifest not found'}), 404
try:
with open(manifest_path, 'r') as f:
manifest = json.load(f)
return jsonify(manifest)
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/docs/<filename>', methods=['GET'])
def download_doc(filename):
"""Download a campaign document"""
doc_path = DOCS_DIR / filename
if not doc_path.exists():
return jsonify({'error': 'Document not found'}), 404
return send_file(doc_path, as_attachment=True)
# ============================================================================
# Health Check
# ============================================================================
@app.route('/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({
'status': 'ok',
'h5_files': len(list(H5_DIR.glob("*.h5"))) if H5_DIR.exists() else 0,
'nodes_loaded': len(nodes_data),
'dates_loaded': len(dates_list)
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3004, debug=False, threaded=True)
# === Additional routes for frontend compatibility ===
@app.route('/api/files', methods=['GET'])
def list_files_alias():
"""Alias for /api/h5/files — used by the SeiSee frontend"""
return list_h5_files()
@app.route('/api/file/<path:filename>', methods=['GET'])
def get_file_info(filename):
"""Get detailed file info including datasets — needed for channel selector"""
h5_path = DATA_DIR / filename
if not h5_path.exists():
return jsonify({'error': 'File not found'}), 404
try:
with h5py.File(h5_path, 'r') as f:
metadata = dict(f['metadata'].attrs) if 'metadata' in f else {}
calibration = dict(f['calibration'].attrs) if 'calibration' in f else {}
datasets = []
def collect_datasets(name, obj):
if isinstance(obj, h5py.Dataset):
datasets.append({
'path': name,
'shape': list(obj.shape),
'dtype': str(obj.dtype),
'chunks': list(obj.chunks) if obj.chunks else None,
'compression': obj.compression
})
f.visititems(collect_datasets)
duration_sec = float(metadata.get('duration_sec', 0))
sample_rate = int(metadata.get('sample_rate_hz', 500))
n_channels = int(metadata.get('n_channels', 4))
n_samples = int(metadata.get('n_samples', 0))
# Human readable duration
hours = int(duration_sec // 3600)
mins = int((duration_sec % 3600) // 60)
duration_human = f'{hours}h{mins:02d}' if hours else f'{mins}min'
return jsonify({
'filename': filename,
'type': 'h5',
'duration_sec': duration_sec,
'duration_human': duration_human,
'sample_rate_hz': sample_rate,
'num_channels': n_channels,
'samples_per_channel': n_samples,
'datasets': datasets,
'calibration': {k: float(v) if hasattr(v, '__float__') else str(v) for k, v in calibration.items()},
'metadata': {k: float(v) if hasattr(v, '__float__') else str(v) for k, v in metadata.items()}
})
except Exception as e:
return jsonify({'error': str(e)}), 500

326
h5_api_server_fixed.py Normal file
View File

@@ -0,0 +1,326 @@
#!/usr/bin/env python3
from flask import Flask, jsonify, request
from flask_cors import CORS
import json
import h5py
import numpy as np
from pathlib import Path
from datetime import datetime
app = Flask(__name__)
CORS(app)
# Load index once at startup
with open('/data/index.json', 'r') as f:
INDEX = json.load(f)
def to_python_type(val):
"""Convert numpy types to Python types for JSON serialization"""
if hasattr(val, 'item'):
return val.item()
return val
H5_DIR = Path('/data/h5')
@app.route('/api/nodes', methods=['GET'])
def get_nodes():
nodes_list = []
for node_id, node_info in INDEX['nodes'].items():
file_count = len(node_info.get('files', []))
nodes_list.append({
'id': node_id,
'position': node_info.get('position', {}),
'file_count': file_count,
'hasDates': file_count > 0
})
return jsonify({
'nodes': nodes_list,
'sampleRateHz': 500
})
@app.route('/api/dates', methods=['GET'])
def get_dates():
return jsonify({'dates': INDEX['dates']})
@app.route('/api/migration-status', methods=['GET'])
def get_migration_status():
"""Status de la conversion RAW -> H5"""
total_files = 345 # Connu du projet
h5_files = list(H5_DIR.glob('*.h5'))
converted = len(h5_files)
return jsonify({
'summary': {
'total_files': total_files,
'converted_files': converted,
'percentage': round(converted / total_files * 100, 1),
'status': 'in_progress' if converted < total_files else 'complete'
},
'h5_files_available': converted
})
@app.route('/api/rms-timeline', methods=['GET'])
def get_rms_timeline():
"""Timeline RMS optimisé - utilise l'INDEX pour filtrer par date"""
date = request.args.get('date')
channel = request.args.get('channel', 'ch0')
window_sec = int(request.args.get('window', 60))
if not date:
return jsonify({'error': 'date parameter required'}), 400
channel_num = int(channel.replace('ch', '').replace('CH', ''))
nodes_data = {}
# Utiliser l'INDEX pour trouver les fichiers de cette date
for node_id, node_info in INDEX['nodes'].items():
for file_info in node_info.get('files', []):
if file_info.get('date') != date:
continue
filename = file_info.get('filename')
h5_path = H5_DIR / filename
if not h5_path.exists():
continue
try:
with h5py.File(h5_path, 'r') as f:
sample_rate = to_python_type(f['metadata'].attrs.get('sample_rate_hz', 500))
start_ts = file_info.get('timestamp', 0)
dataset_name = f'calibrated_data/channel_{channel_num + 1}'
if dataset_name not in f:
continue
dataset = f[dataset_name]
total_samples = dataset.shape[0]
window_samples = int(window_sec * sample_rate)
# Calculer RMS par fenêtre (max 100 points par fichier)
timeline = []
step = max(window_samples, total_samples // 100)
for i in range(0, min(total_samples, step * 100), step):
end_idx = min(i + window_samples, total_samples)
chunk = dataset[i:end_idx]
rms = float(np.sqrt(np.mean(chunk ** 2)))
ts = start_ts + (i / sample_rate)
timeline.append({'ts': ts, 'rms': rms})
if node_id not in nodes_data:
nodes_data[node_id] = []
nodes_data[node_id].extend(timeline)
except Exception as e:
continue
return jsonify({
'date': date,
'channel': channel,
'nodes': nodes_data
})
@app.route('/api/h5/data', methods=['GET'])
@app.route('/api/data', methods=['GET'])
def get_waveform_data():
"""Données waveform - accepte soit file= soit node=+date="""
filename = request.args.get('file')
node_id = request.args.get('node')
date = request.args.get('date')
channel = request.args.get('channel', 'ch0')
start = float(request.args.get('start', 0))
duration = float(request.args.get('duration', 10))
# Trouver le fichier H5
h5_file = None
if filename:
# Mode direct: fichier spécifié
h5_file = H5_DIR / filename
elif node_id and date:
# Mode lookup: chercher via node_id et date
node_info = INDEX['nodes'].get(node_id)
if node_info:
for file_info in node_info.get('files', []):
if file_info.get('date') == date:
h5_file = H5_DIR / file_info.get('filename', '')
break
else:
return jsonify({'error': 'file or (node and date) required'}), 400
if not h5_file or not h5_file.exists():
return jsonify({'error': 'H5 file not found', 'path': str(h5_file)}), 404
try:
with h5py.File(h5_file, 'r') as f:
sample_rate = f['metadata'].attrs['sample_rate_hz']
channel_num = int(channel.replace('ch', '').replace('CH', ''))
# Lire les données calibrées
dataset = f[f'calibrated_data/channel_{channel_num + 1}']
start_sample = int(start * sample_rate)
end_sample = int((start + duration) * sample_rate)
# Limiter à la taille du dataset
end_sample = min(end_sample, dataset.shape[0])
if start_sample >= dataset.shape[0]:
return jsonify({'error': 'start time out of range'}), 400
data = dataset[start_sample:end_sample]
# Calculer stats
rms = float(np.sqrt(np.mean(data ** 2)))
peak = float(np.max(np.abs(data)))
return jsonify({
'node_id': node_id,
'date': date,
'channel': channel,
'start': start,
'duration': duration,
'sample_rate': to_python_type(sample_rate),
'data': data.tolist(),
'stats': {
'rms': rms,
'peak': peak,
'samples': len(data)
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/h5/files', methods=['GET'])
def get_h5_files():
"""Liste des fichiers H5 disponibles avec métadonnées"""
files = []
for h5_path in H5_DIR.glob('*.h5'):
try:
with h5py.File(h5_path, 'r') as f:
filename = h5_path.name
# Parse: auto_228_064210_b50_rsn51848_seq1_1597256250.h5
parts = filename.replace('.h5', '').split('_')
node_id = parts[1] if len(parts) > 1 else 'unknown'
# Find date from INDEX
date = 'unknown'
node_info = INDEX['nodes'].get(node_id)
if node_info:
for file_info in node_info.get('files', []):
if file_info.get('filename') == filename:
date = file_info.get('date', 'unknown')
break
files.append({
'filename': filename,
'nodeId': node_id,
'date': date,
'size_mb': round(h5_path.stat().st_size / 1024 / 1024, 2),
'duration_sec': to_python_type(f['metadata'].attrs.get('duration_sec', 0)),
'sample_rate': to_python_type(f['metadata'].attrs.get('sample_rate_hz', 500)),
'channels': to_python_type(f['metadata'].attrs.get('n_channels', 4))
})
except Exception as e:
pass
return jsonify({'files': files, 'count': len(files)})
@app.route('/api/h5/coverage', methods=['GET'])
def get_h5_coverage():
"""Matrice de couverture nodes x dates"""
coverage = {}
for node_id, node_info in INDEX['nodes'].items():
node_dates = [f['date'] for f in node_info.get('files', []) if 'date' in f]
coverage[node_id] = node_dates
return jsonify({
'coverage': coverage,
'total_nodes': len(coverage),
'total_dates': len(INDEX['dates'])
})
@app.route('/api/h5/gaps', methods=['GET'])
def get_h5_gaps():
"""Identify gaps in H5 data coverage per node"""
gaps = []
for node_id, node_info in INDEX['nodes'].items():
files = sorted(node_info.get('files', []), key=lambda x: x.get('timestamp', 0))
for i in range(len(files) - 1):
current = files[i]
next_file = files[i + 1]
current_end = current.get('timestamp', 0) + current.get('duration_sec', 0)
next_start = next_file.get('timestamp', 0)
gap_seconds = next_start - current_end
# Report gaps > 1 hour
if gap_seconds > 3600:
gaps.append({
'node_id': node_id,
'gap_start': current_end,
'gap_end': next_start,
'gap_hours': round(gap_seconds / 3600, 2),
'before_file': current.get('filename'),
'after_file': next_file.get('filename')
})
return jsonify({
'gaps': gaps,
'total_gaps': len(gaps)
})
@app.route('/api/chat', methods=['POST'])
def chat():
"""Endpoint chat assistant (mock)"""
data = request.json
message = data.get('message', '')
return jsonify({
'response': f"[Mock] Vous avez dit: {message}",
'timestamp': datetime.now().isoformat()
})
@app.route('/api/docs/manifest', methods=['GET'])
def get_docs_manifest():
"""Manifest de la documentation de campagne"""
return jsonify({
'campaign': {
'name': 'SeaKESP Sète 2020',
'start_date': '2020-07-11',
'end_date': '2020-09-22',
'duration_days': 46,
'location': 'Sète, Méditerranée, France',
'coordinates': {'lat': 43.40, 'lon': 3.70}
},
'timeline': [
{'date': '2020-07-11', 'event': 'Début du déploiement', 'type': 'deployment'},
{'date': '2020-08-08', 'event': 'Premiers enregistrements', 'type': 'recording'},
{'date': '2020-08-12', 'event': 'Campagne principale', 'type': 'recording'},
{'date': '2020-08-16', 'event': 'Fin campagne principale', 'type': 'recording'},
{'date': '2020-09-22', 'event': 'Récupération équipements', 'type': 'recovery'}
],
'documents': [
{'name': 'Rapport Gandalf', 'type': 'report', 'available': False},
{'name': 'SPS Preplots', 'type': 'technical', 'available': False},
{'name': 'Paramètres acquisition', 'type': 'technical', 'available': False}
],
'stats': {
'total_nodes': len(INDEX['nodes']),
'total_dates': len(INDEX['dates']),
'total_files': INDEX.get('total_files', 0),
'sample_rate_hz': 500,
'channels': 4
}
})
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'nodes': len(INDEX['nodes']), 'dates': len(INDEX['dates'])})
if __name__ == '__main__':
print(f"Loaded {len(INDEX['nodes'])} nodes, {len(INDEX['dates'])} dates")
print(f"H5 directory: {H5_DIR}")
app.run(host='0.0.0.0', port=3004)

BIN
h5_data.db Normal file

Binary file not shown.

2685
index.html.final.bak Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2029
index.html.v3 Normal file

File diff suppressed because it is too large Load Diff

86
index_all_v2.py Normal file
View File

@@ -0,0 +1,86 @@
import os
import re
import json
import csv
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
# Pattern pour extraire les infos du nom de fichier
FILENAME_PATTERN = re.compile(r'auto_.*?_b(\d+)_.*?_(\d{10})\.h5$', re.IGNORECASE)
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
def load_node_positions(csv_path):
positions = {}
if not csv_path.exists(): return positions
with open(csv_path, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return positions
headers = lines[3].strip().split(',')
try:
node_code_idx = headers.index('NodeCode')
easting_idx = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
northing_idx = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
except: return positions
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[node_code_idx].strip()
positions[nid] = {
'easting': float(parts[easting_idx]),
'northing': float(parts[northing_idx]),
'depth': float(parts[headers.index('Aslaid Depth')]) if 'Aslaid Depth' in headers else 0.0
}
except: continue
return positions
def scan_all():
index = {}
pos = load_node_positions(POSITIONS_CSV)
print(f"Positions chargées: {len(pos)}")
file_count = 0
for root in DATA_ROOTS:
print(f"Scan de {root}...")
for h5_file in root.rglob("*.h5"):
# Extraction ID node et timestamp
match = re.search(r'_b(\d+)_.*?(\d{10})\.h5$', h5_file.name)
if not match: continue
node_id = match.group(1)
ts = int(match.group(2))
date_str = datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
if node_id not in index:
index[node_id] = {'id': node_id, 'position': pos.get(node_id), 'dates': {}, 'hasDates': True}
if date_str not in index[node_id]['dates']:
index[node_id]['dates'][date_str] = []
index[node_id]['dates'][date_str].append({
'path': str(h5_file),
'timestamp': ts,
'channels': ['ch0', 'ch1', 'ch2', 'ch3'],
'size_bytes': h5_file.stat().st_size
})
file_count += 1
# Ajouter les nodes sans fichiers mais avec position
for nid, p in pos.items():
if nid not in index:
index[nid] = {'id': nid, 'position': p, 'dates': {}, 'hasDates': False}
full_index = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': 200,
'nodes': index,
'dates': sorted(list(set(d for n in index.values() for d in n['dates'].keys())))
}
with open(OUTPUT_INDEX, 'w') as f: json.dump(full_index, f, indent=2)
print(f"Index total généré: {file_count} fichiers, {len(index)} nodes.")
if __name__ == '__main__': scan_all()

69
index_all_v3.py Normal file
View File

@@ -0,0 +1,69 @@
import os, re, json, csv
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
FILENAME_PATTERN = re.compile(r'_b(\d+)_.*?_(\d{10})\.h5$', re.IGNORECASE)
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
di = headers.index('Aslaid Depth') if 'Aslaid Depth' in headers else -1
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {'easting': float(parts[ei]), 'northing': float(parts[oi]), 'depth': float(parts[di]) if di != -1 else 0.0}
except: continue
return positions
def scan():
pos = load_pos()
index = {}
file_count = 0
for root in DATA_ROOTS:
print(f"Scanning {root}...")
for h5_file in root.rglob("*.h5"):
match = FILENAME_PATTERN.search(h5_file.name)
if not match: continue
nid, ts = match.group(1), int(match.group(2))
# Utilisation de la date du dossier parent si possible, sinon du timestamp
date_str = datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
# Forcer la date du dossier (plus fiable pour l'utilisateur)
for p in h5_file.parents:
if re.match(r'2020-09-\d{2}', p.name):
date_str = p.name
break
if nid not in index:
index[nid] = {'id': nid, 'position': pos.get(nid), 'dates': {}, 'hasDates': True}
if date_str not in index[nid]['dates']:
index[nid]['dates'][date_str] = []
index[nid]['dates'][date_str].append({'path': str(h5_file), 'timestamp': ts, 'channels': ['ch0', 'ch1', 'ch2', 'ch3']})
file_count += 1
for nid, p in pos.items():
if nid not in index: index[nid] = {'id': nid, 'position': p, 'dates': {}, 'hasDates': False}
full = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': 200,
'nodes': index,
'dates': sorted(list(set(d for n in index.values() for d in n['dates'].keys())))
}
with open(OUTPUT_INDEX, 'w') as f: json.dump(full, f, indent=2)
print(f"Index: {file_count} files, {len(index)} nodes, {len(full['dates'])} dates.")
if __name__ == '__main__': scan()

105
index_data_only.py Normal file
View File

@@ -0,0 +1,105 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[headers.index('Aslaid Depth')]) if 'Aslaid Depth' in headers else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
nodes = {}
all_dates = set()
file_count = 0
print("🔍 Scanning ONLY 'data' H5 files (ignoring 'aux')...")
all_h5_files = []
for root in DATA_ROOTS:
all_h5_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_h5_files):
# FILTRE : Uniquement les fichiers contenant "data"
if "_data_" not in h5_path.name.lower():
continue
try:
match = re.search(r'auto_(\d+)_(\d{6})_b(\d+)_.*?_(\d{10})\.h5$', h5_path.name)
if not match: continue
julian_day = int(match.group(1))
time_str = match.group(2)
node_id = match.group(3)
date_ref = datetime(2020, 1, 1) + timedelta(days=julian_day - 1)
date_str = date_ref.strftime('%Y-%m-%d')
h, m, s = int(time_str[:2]), int(time_str[2:4]), int(time_str[4:6])
actual_start_ts = int(datetime(2020, 1, 1).timestamp() + (julian_day - 1) * 86400 + h * 3600 + m * 60 + s)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
duration = f['adc_values'].shape[0] / SAMPLE_RATE
actual_end_ts = actual_start_ts + duration
all_dates.add(date_str)
if node_id not in nodes:
nodes[node_id] = {
'id': node_id,
'position': pos.get(node_id),
'files': []
}
# On extrait le canal du nom de fichier pour un matching plus précis
channel_match = re.search(r'_ch(\d+)_', h5_path.name)
channel = f"ch{channel_match.group(1)}" if channel_match else "ch0"
nodes[node_id]['files'].append({
'path': str(h5_path),
'start': actual_start_ts,
'end': actual_end_ts,
'julian': julian_day,
'channel': channel # Canal spécifique au fichier
})
file_count += 1
except: continue
result = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': nodes,
'dates': sorted(list(all_dates))
}
with open(OUTPUT_INDEX, 'w') as f:
json.dump(result, f, indent=2)
print(f"✅ Index updated: {file_count} 'data' files, {len(nodes)} nodes.")
if __name__ == '__main__': scan()

57
index_time_ranges.py Normal file
View File

@@ -0,0 +1,57 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def scan():
index = {}
file_count = 0
print("Scanning H5 files for time ranges...")
# On récupère d'abord les fichiers
all_files = []
for root in DATA_ROOTS:
all_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_files):
try:
# On extrait ID node du nom de fichier
match = re.search(r'_b(\d+)_', h5_path.name)
if not match: continue
nid = match.group(1)
# On ouvre le fichier pour avoir le VRAI timestamp et la durée
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
ds = f['adc_values']
start_ts = int(ds.attrs.get('timestamp', 0))
if start_ts == 0: continue
duration = ds.shape[0] / SAMPLE_RATE
end_ts = start_ts + duration
if nid not in index: index[nid] = []
index[nid].append({
'path': str(h5_path),
'start': start_ts,
'end': end_ts,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index
with open(OUTPUT_INDEX, 'w') as f:
json.dump({
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'files_by_node': index
}, f)
print(f"Index généré: {file_count} fichiers avec plages temporelles réelles.")
if __name__ == '__main__': scan()

87
index_time_ranges_v2.py Normal file
View File

@@ -0,0 +1,87 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
di = headers.index('Aslaid Depth') if 'Aslaid Depth' in headers else -1
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[di]) if di != -1 else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
index = {}
file_count = 0
print(f"Scanning H5 files... Positions loaded: {len(pos)}")
all_files = []
for root in DATA_ROOTS:
all_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_files):
try:
match = re.search(r'_b(\d+)_', h5_path.name)
if not match: continue
nid = match.group(1)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
ds = f['adc_values']
start_ts = int(ds.attrs.get('timestamp', 0))
if start_ts == 0: continue
duration = ds.shape[0] / SAMPLE_RATE
end_ts = start_ts + duration
if nid not in index:
index[nid] = {
'id': nid,
'position': pos.get(nid),
'files': []
}
index[nid]['files'].append({
'path': str(h5_path),
'start': start_ts,
'end': end_ts,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index
with open(OUTPUT_INDEX, 'w') as f:
json.dump({
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': index
}, f)
print(f"Index généré: {file_count} fichiers, {len(index)} nodes avec positions.")
if __name__ == '__main__': scan()

108
index_tmp.py Normal file
View File

@@ -0,0 +1,108 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/tmp/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[headers.index('Aslaid Depth')]) if 'Aslaid Depth' in headers else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
nodes = {}
all_dates = set()
file_count = 0
print("🔍 Scanning all H5 files (Trusting filenames/folders for dates)...")
all_h5_files = []
for root in DATA_ROOTS:
all_h5_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_h5_files):
try:
# Pattern: auto_{julian}_{time}_b{node}_..._{ts}.h5
match = re.search(r'auto_(\d+)_(\d{6})_b(\d+)_.*?_(\d{10})\.h5$', h5_path.name)
if not match: continue
julian_day = int(match.group(1))
time_str = match.group(2)
node_id = match.group(3)
internal_ts = int(match.group(4))
# Calculer la date réelle à partir du Julian Day (2020)
# Julian 1 = Jan 1. Julian 255 = Sept 11.
date_ref = datetime(2020, 1, 1) + timedelta(days=julian_day - 1)
date_str = date_ref.strftime('%Y-%m-%d')
# Heure du fichier
hours = int(time_str[:2])
minutes = int(time_str[2:4])
seconds = int(time_str[4:6])
# Timestamp calculé (plus fiable pour le matching que l'interne buggé)
actual_start_ts = int(datetime(2020, 1, 1).timestamp() + (julian_day - 1) * 86400 + hours * 3600 + minutes * 60 + seconds)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
duration = f['adc_values'].shape[0] / SAMPLE_RATE
actual_end_ts = actual_start_ts + duration
all_dates.add(date_str)
if node_id not in nodes:
nodes[node_id] = {
'id': node_id,
'position': pos.get(node_id),
'files': []
}
nodes[node_id]['files'].append({
'path': str(h5_path),
'start': actual_start_ts,
'end': actual_end_ts,
'julian': julian_day,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index complet
result = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': nodes,
'dates': sorted(list(all_dates))
}
with open(OUTPUT_INDEX, 'w') as f:
json.dump(result, f, indent=2)
print(f"✅ Indexing complete: {file_count} files, {len(nodes)} nodes, {len(all_dates)} dates.")
print(f"📅 Dates covered: {sorted(list(all_dates))}")
if __name__ == '__main__': scan()

96
index_ultimate.py Normal file
View File

@@ -0,0 +1,96 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
di = headers.index('Aslaid Depth') if 'Aslaid Depth' in headers else -1
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[di]) if di != -1 else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
nodes = {}
all_dates = set()
file_count = 0
print("🔍 Scanning all H5 files...")
all_h5_files = []
for root in DATA_ROOTS:
all_h5_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_h5_files):
try:
match = re.search(r'_b(\d+)_', h5_path.name)
if not match: continue
nid = match.group(1)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
ds = f['adc_values']
start_ts = int(ds.attrs.get('timestamp', 0))
if start_ts == 0: continue
duration = ds.shape[0] / SAMPLE_RATE
end_ts = start_ts + duration
# Ajouter la date à la liste globale
date_str = datetime.fromtimestamp(start_ts).strftime('%Y-%m-%d')
all_dates.add(date_str)
if nid not in nodes:
nodes[nid] = {
'id': nid,
'position': pos.get(nid),
'files': []
}
nodes[nid]['files'].append({
'path': str(h5_path),
'start': start_ts,
'end': end_ts,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index complet
result = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': nodes,
'dates': sorted(list(all_dates))
}
with open(OUTPUT_INDEX, 'w') as f:
json.dump(result, f, indent=2)
print(f"✅ Indexing complete: {file_count} files, {len(nodes)} nodes, {len(all_dates)} dates.")
print(f"📅 Dates covered: {sorted(list(all_dates))}")
if __name__ == '__main__': scan()

108
index_ultimate_v2.py Normal file
View File

@@ -0,0 +1,108 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[headers.index('Aslaid Depth')]) if 'Aslaid Depth' in headers else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
nodes = {}
all_dates = set()
file_count = 0
print("🔍 Scanning all H5 files (Trusting filenames/folders for dates)...")
all_h5_files = []
for root in DATA_ROOTS:
all_h5_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_h5_files):
try:
# Pattern: auto_{julian}_{time}_b{node}_..._{ts}.h5
match = re.search(r'auto_(\d+)_(\d{6})_b(\d+)_.*?_(\d{10})\.h5$', h5_path.name)
if not match: continue
julian_day = int(match.group(1))
time_str = match.group(2)
node_id = match.group(3)
internal_ts = int(match.group(4))
# Calculer la date réelle à partir du Julian Day (2020)
# Julian 1 = Jan 1. Julian 255 = Sept 11.
date_ref = datetime(2020, 1, 1) + timedelta(days=julian_day - 1)
date_str = date_ref.strftime('%Y-%m-%d')
# Heure du fichier
hours = int(time_str[:2])
minutes = int(time_str[2:4])
seconds = int(time_str[4:6])
# Timestamp calculé (plus fiable pour le matching que l'interne buggé)
actual_start_ts = int(datetime(2020, 1, 1).timestamp() + (julian_day - 1) * 86400 + hours * 3600 + minutes * 60 + seconds)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
duration = f['adc_values'].shape[0] / SAMPLE_RATE
actual_end_ts = actual_start_ts + duration
all_dates.add(date_str)
if node_id not in nodes:
nodes[node_id] = {
'id': node_id,
'position': pos.get(node_id),
'files': []
}
nodes[node_id]['files'].append({
'path': str(h5_path),
'start': actual_start_ts,
'end': actual_end_ts,
'julian': julian_day,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index complet
result = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': nodes,
'dates': sorted(list(all_dates))
}
with open(OUTPUT_INDEX, 'w') as f:
json.dump(result, f, indent=2)
print(f"✅ Indexing complete: {file_count} files, {len(nodes)} nodes, {len(all_dates)} dates.")
print(f"📅 Dates covered: {sorted(list(all_dates))}")
if __name__ == '__main__': scan()

63
precompute_all_v4.py Normal file
View File

@@ -0,0 +1,63 @@
import json, sys, os, numpy as np, h5py
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
INDEX_PATH = Path("/mnt/kingston/seismic_webapp/data/index.json")
OUTPUT_DIR = Path("/mnt/kingston/seismic_webapp/data/rms_cache")
SAMPLE_RATE = 200
def fix_path(p):
p = p.replace('\\', '/')
if p.startswith('F:/'): return '/mnt/kingston/' + p[3:]
if p.startswith('E:/'): return '/mnt/data_sdb1/' + p[3:]
return p
def compute_rms(h5_path):
h5_path = fix_path(h5_path)
if not os.path.exists(h5_path): return None
try:
with h5py.File(h5_path, 'r') as f:
ds = f['adc_values']
start_ts = int(ds.attrs.get('timestamp', 0))
if start_ts == 0: return None
# On prend 5000 samples pour un RMS représentatif
samples = ds[0:5000]
rms = float(np.sqrt(np.mean(samples.astype(np.float64)**2)))
return [{'ts': start_ts, 'rms': rms}]
except: return None
def main():
with open(INDEX_PATH, 'r') as f: index = json.load(f)
nodes = index.get('nodes', {})
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Toutes les dates du système
all_dates = set()
for n in nodes.values():
if 'dates' in n: all_dates.update(n['dates'].keys())
print(f"Dates à traiter: {sorted(list(all_dates))}")
channel = "ch0"
for date in sorted(list(all_dates)):
output_file = OUTPUT_DIR / f"rms_{date}_{channel}.json"
# On force la régénération pour être sûr d'avoir tout
print(f"Processing {date}...")
results = {}
for nid, node in tqdm(nodes.items(), desc=f"Nodes {date}"):
files = node.get('dates', {}).get(date, [])
# On cherche les fichiers data prioritaires
target = next((f for f in files if '_data_' in f['path'] and f'_{channel}_' in f['path']), None)
if not target and files: target = files[0] # Fallback
if target:
data = compute_rms(target['path'])
if data: results[nid] = data
if results:
with open(output_file, 'w') as f:
json.dump({'date':date, 'channel':channel, 'nodes':results}, f)
print(f"Sauvegardé {output_file.name}: {len(results)} nodes")
if __name__ == '__main__': main()

62
precompute_ultimate.py Normal file
View File

@@ -0,0 +1,62 @@
import json, sys, os, numpy as np, h5py, re
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
INDEX_PATH = Path("/mnt/kingston/seismic_webapp/data/index.json")
OUTPUT_DIR = Path("/mnt/kingston/seismic_webapp/data/rms_cache")
SAMPLE_RATE = 200
def fix_path(p):
p = p.replace('\\', '/')
if p.startswith('F:/'): return '/mnt/kingston/' + p[3:]
if p.startswith('E:/'): return '/mnt/data_sdb1/' + p[3:]
return p
def compute_rms(h5_path):
h5_path = fix_path(h5_path)
if not os.path.exists(h5_path): return None
try:
# Extraire le timestamp réel du nom de fichier (Julian day)
match = re.search(r'auto_(\d+)_(\d{6})_b', os.path.basename(h5_path))
if not match: return None
julian, time_str = int(match.group(1)), match.group(2)
h, m, s = int(time_str[:2]), int(time_str[2:4]), int(time_str[4:6])
start_ts = int(datetime(2020, 1, 1).timestamp() + (julian - 1) * 86400 + h * 3600 + m * 60 + s)
with h5py.File(h5_path, 'r') as f:
ds = f['adc_values']
samples = ds[0:5000]
rms = float(np.sqrt(np.mean(samples.astype(np.float64)**2)))
return [{'ts': start_ts, 'rms': rms}]
except: return None
def main():
with open(INDEX_PATH, 'r') as f: index = json.load(f)
nodes = index.get('nodes', {})
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
for date in index['dates']:
channel = "ch0"
output_file = OUTPUT_DIR / f"rms_{{date}}_{{channel}}.json"
print(f"Processing {date}...")
results = {}
for nid, node in tqdm(nodes.items(), desc=f"Nodes {date}"):
files = node.get('files', [])
# Filtrer les fichiers par Julian Day correspondant à la date
dt = datetime.strptime(date, '%Y-%m-%d')
target_julian = dt.timetuple().tm_yday
target = next((f for f in files if f['julian'] == target_julian and f'_{{channel}}_' in f['path']), None)
if not target and files:
target = next((f for f in files if f['julian'] == target_julian), None)
if target:
data = compute_rms(target['path'])
if data: results[nid] = data
if results:
with open(output_file, 'w') as f:
json.dump({'date':date, 'channel':channel, 'nodes':results}, f)
if __name__ == '__main__': main()

1
real_shots.json Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

22
scripts/check_node29.py Executable file
View File

@@ -0,0 +1,22 @@
import json
data = json.load(open(r'F:\seismic_webapp\data\index.json'))
node29 = data['nodes'].get('29')
if node29:
print(f"Node 29:")
print(f" Position: {node29.get('position')}")
print(f" Dates disponibles: {list(node29.get('dates', {}).keys())}")
for date, files in node29.get('dates', {}).items():
print(f" {date}: {len(files)} fichiers")
for f in files[:2]:
print(f" - {f['path']}")
else:
print("Node 29 non trouvé dans l'index")
print("\n--- Tous les nodes avec données ---")
for node_id, node in data['nodes'].items():
if node.get('dates') and len(node['dates']) > 0:
has_pos = node.get('position') is not None
dates = list(node['dates'].keys())
print(f"Node {node_id}: pos={has_pos}, dates={dates}")

22
scripts/check_positions.py Executable file
View File

@@ -0,0 +1,22 @@
import json
data = json.load(open(r'F:\seismic_webapp\data\index.json'))
nodes_with_data = [n for n in data['nodes'].values() if n.get('dates') and len(n['dates']) > 0]
print(f'Nodes avec donnees: {len(nodes_with_data)}')
print('\n--- Nodes avec donnees et leurs positions ---')
for n in nodes_with_data[:10]:
pos = n.get('position')
has_pos = pos and pos.get('easting') and pos.get('northing')
print(f"Node {n['id']}: hasPos={has_pos}, pos={pos}")
print('\n--- Nodes avec donnees SANS position valide ---')
no_pos_count = 0
for n in nodes_with_data:
pos = n.get('position')
if not pos or not pos.get('easting') or not pos.get('northing'):
print(f"Node {n['id']}: pos={pos}")
no_pos_count += 1
print(f'\nTotal nodes sans position valide: {no_pos_count}')

35
scripts/debug_inventory.py Executable file
View File

@@ -0,0 +1,35 @@
import json
d = json.load(open(r'F:\seismic_webapp\inventory.json'))
# Verifier quelques fichiers
print("=== EXEMPLES DE FICHIERS ===")
for f in d[:5]:
print(f"File: {f['filename']}")
print(f" Bumper: {f['bumper_id']}, Channel: {f['channel']}")
print(f" Samples: {f['samples']}, Epoch: {f['epoch_time']}")
print()
# Compter les bumpers uniques
bumpers = set(f['bumper_id'] for f in d if f['bumper_id'])
print(f"Bumpers uniques: {len(bumpers)}")
print(f"Liste: {sorted(bumpers, key=lambda x: int(x) if x and x.isdigit() else 999)[:30]}")
# Verifier le probleme des samples
print("\n=== FICHIERS AVEC GROS SAMPLES ===")
big_files = [f for f in d if f['samples'] > 100000000]
for f in big_files[:5]:
print(f" {f['filename']}: {f['samples']} samples = {f['samples']/200/3600:.1f}h")
# Stats par bumper
from collections import defaultdict
by_bumper = defaultdict(lambda: {'files': 0, 'channels': set()})
for f in d:
if f['bumper_id']:
by_bumper[f['bumper_id']]['files'] += 1
if f['channel']:
by_bumper[f['bumper_id']]['channels'].add(f['channel'])
print(f"\n=== PAR BUMPER (premiers 20) ===")
for b in sorted(by_bumper.keys(), key=lambda x: int(x) if x.isdigit() else 999)[:20]:
s = by_bumper[b]
print(f" b{b}: {s['files']} files, channels: {sorted(s['channels'])}")

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""
Script d'extraction de données H5 calibrées (format 2026).
Lit calibrated_data/channel_X (valeurs physiques avec unités).
"""
import argparse
import json
import sys
import h5py
import numpy as np
def extract_window(file_path: str, channel: int, start_ts: int, duration_sec: int) -> dict:
try:
with h5py.File(file_path, 'r') as f:
# Métadonnées
meta = f['metadata']
sample_rate = meta.attrs['sample_rate_hz']
file_duration = meta.attrs['duration_sec']
total_samples = meta.attrs['n_samples']
# Dataset calibré
dataset = f[f'calibrated_data/channel_{channel}']
# Calcul indices (si start_ts = 0, on prend depuis le début)
start_idx = int(start_ts * sample_rate) if start_ts > 0 else 0
num_samples = int(duration_sec * sample_rate) if duration_sec > 0 else total_samples
end_idx = min(start_idx + num_samples, total_samples)
# Extraire
samples = dataset[start_idx:end_idx]
# Unité selon le canal
unit = 'm/s' if channel in [1, 2, 3] else 'Pa'
channel_name = f'Geophone {channel}' if channel in [1, 2, 3] else 'Hydrophone'
return {
"samples": samples.tolist(),
"start_idx": int(start_idx),
"end_idx": int(end_idx),
"total_samples": int(total_samples),
"sample_rate": int(sample_rate),
"duration_sec": float(file_duration),
"channel": channel,
"channel_name": channel_name,
"unit": unit,
"stats": {
"min": float(np.min(samples)),
"max": float(np.max(samples)),
"mean": float(np.mean(samples)),
"std": float(np.std(samples)),
"rms": float(np.sqrt(np.mean(samples**2)))
},
"source": "calibrated_h5_2026"
}
except Exception as e:
return {"error": str(e)}
def main():
parser = argparse.ArgumentParser(description='Extraction H5 calibré')
parser.add_argument('--file', required=True, help='Fichier H5')
parser.add_argument('--channel', type=int, required=True, help='Canal 1-4')
parser.add_argument('--start', type=int, default=0, help='Offset secondes (0=début)')
parser.add_argument('--duration', type=int, default=0, help='Durée secondes (0=tout)')
args = parser.parse_args()
result = extract_window(args.file, args.channel, args.start, args.duration)
print(json.dumps(result))
if __name__ == '__main__':
main()

132
scripts/extract_hdf5_window.py Executable file
View File

@@ -0,0 +1,132 @@
"""
Script d'extraction de fenêtres de données HDF5.
Appelé par le backend Node.js pour lire des portions de données ADC
sans charger tout le fichier en mémoire.
Usage:
python extract_hdf5_window.py --file <path> --channel <ch0-ch3> --start <timestamp> --duration <seconds>
"""
import argparse
import json
import sys
from pathlib import Path
try:
import h5py
import numpy as np
except ImportError as e:
print(json.dumps({"error": f"Module manquant: {e}"}))
sys.exit(1)
SAMPLE_RATE = 200 # Hz
def extract_window(file_path: str, channel: str, start_ts: int, duration_sec: int) -> dict:
"""
Extrait une fenêtre de données ADC d'un fichier HDF5.
Args:
file_path: Chemin vers le fichier H5
channel: Canal à extraire (ch0, ch1, ch2, ch3)
start_ts: Timestamp de début (secondes Unix)
duration_sec: Durée en secondes
Returns:
dict avec les échantillons et métadonnées
"""
file_path = Path(file_path)
if not file_path.exists():
return {"error": f"Fichier non trouvé: {file_path}"}
try:
with h5py.File(file_path, 'r') as f:
# Chaque fichier HDF5 contient un seul dataset 'adc_values'
# Le canal est déterminé par le nom du fichier, pas par un chemin interne
if 'adc_values' not in f:
# Lister les datasets disponibles pour debug
available = []
def visit(name, obj):
if isinstance(obj, h5py.Dataset):
available.append(name)
f.visititems(visit)
return {"error": f"Dataset 'adc_values' non trouvé. Disponibles: {available}"}
dataset = f['adc_values']
# Récupérer les attributs de temps si disponibles
# Chercher d'abord dans les attributs du dataset, puis du fichier
file_start_ts = None
if 'timestamp' in dataset.attrs:
file_start_ts = int(dataset.attrs['timestamp'])
elif 'start_time' in dataset.attrs:
file_start_ts = int(dataset.attrs['start_time'])
elif 'timestamp' in f.attrs:
file_start_ts = int(f.attrs['timestamp'])
elif 'start_time' in f.attrs:
file_start_ts = int(f.attrs['start_time'])
# Calculer les indices de début et fin
total_samples = dataset.shape[0]
if file_start_ts is not None:
# Offset par rapport au début du fichier
offset_sec = max(0, start_ts - file_start_ts)
start_idx = int(offset_sec * SAMPLE_RATE)
else:
# Pas d'info de temps, prendre depuis le début
start_idx = 0
num_samples = int(duration_sec * SAMPLE_RATE)
end_idx = min(start_idx + num_samples, total_samples)
# Limiter pour éviter les gros payloads (max 60 secondes = 12000 samples)
max_samples = 60 * SAMPLE_RATE
if end_idx - start_idx > max_samples:
end_idx = start_idx + max_samples
# Extraire les données (lecture partielle, pas tout en RAM)
samples = dataset[start_idx:end_idx]
# Garder en numpy pour les stats
samples_array = np.array(samples) if not isinstance(samples, np.ndarray) else samples
return {
"samples": samples.tolist() if isinstance(samples, np.ndarray) else samples,
"start_idx": start_idx,
"end_idx": end_idx,
"total_samples": total_samples,
"file_start_ts": file_start_ts,
"channel": channel,
"stats": {
"min": float(np.min(samples_array)) if len(samples_array) > 0 else None,
"max": float(np.max(samples_array)) if len(samples_array) > 0 else None,
"mean": float(np.mean(samples_array)) if len(samples_array) > 0 else None,
"rms": float(np.sqrt(np.mean(samples_array**2))) if len(samples_array) > 0 else None,
}
}
except Exception as e:
return {"error": str(e)}
def main():
parser = argparse.ArgumentParser(description='Extraction de fenêtre HDF5')
parser.add_argument('--file', required=True, help='Chemin du fichier H5')
parser.add_argument('--channel', required=True, help='Canal (ch0-ch3)')
parser.add_argument('--start', type=int, required=True, help='Timestamp de début')
parser.add_argument('--duration', type=int, default=10, help='Durée en secondes')
args = parser.parse_args()
result = extract_window(args.file, args.channel, args.start, args.duration)
# Sortie JSON pour le backend Node.js
print(json.dumps(result))
if __name__ == '__main__':
main()

479
scripts/generate_inventory.py Executable file
View File

@@ -0,0 +1,479 @@
#!/usr/bin/env python3
"""
Script pour générer un inventaire HTML de tous les fichiers HDF5.
Affiche: numéro de bumper, canal, date/heure début, date/heure fin, durée, nombre d'échantillons.
"""
import os
import sys
import json
import h5py
import re
from datetime import datetime
from pathlib import Path
from collections import defaultdict
# Configuration
SAMPLE_RATE = 200 # Hz
DATA_DIRS = [
r"F:\2020-09-11",
r"E:\2020-09-11",
r"E:\2020-09-14",
]
def parse_filename(filename):
"""
Parse le nom de fichier HDF5 pour extraire les infos.
Formats supportes:
- auto_260_061316_b0_13_212626_data_rsn84614_seq1_ch0_1598976585.h5 (bumper = 13)
- auto_255_061140_b119_12_230609_data_rsn5725_seq1_ch0_1599065292.h5 (bumper = 119)
"""
bumper_id = None
# Format 1: _b0_XX_ (ex: _b0_13_)
bumper_match = re.search(r'_b0_(\d+)_', filename)
if bumper_match:
bumper_id = bumper_match.group(1)
else:
# Format 2: _bXXX_ (ex: _b119_)
bumper_match = re.search(r'_b(\d+)_', filename)
if bumper_match:
bumper_id = bumper_match.group(1)
# Extraire le canal (ch0, ch1, ch2, ch3, ch5, ch6, ch7, ch15)
channel_match = re.search(r'_(ch\d+)_', filename)
channel = channel_match.group(1) if channel_match else None
# Extraire l'epoch time (dernier nombre avant .h5)
epoch_match = re.search(r'_(\d{10})\.h5$', filename)
epoch_time = int(epoch_match.group(1)) if epoch_match else None
# Type de fichier (data ou aux)
file_type = 'data' if '_data_' in filename else 'aux' if '_aux_' in filename else 'unknown'
return {
'bumper_id': bumper_id,
'channel': channel,
'epoch_time': epoch_time,
'file_type': file_type
}
def get_hdf5_info(filepath):
"""
Ouvre le fichier HDF5 et récupère le nombre d'échantillons.
"""
try:
with h5py.File(filepath, 'r') as f:
# Chercher le dataset adc_values
if 'adc_values' in f:
samples = f['adc_values'].shape[0]
return {'samples': samples, 'error': None}
else:
# Lister les datasets disponibles
datasets = list(f.keys())
return {'samples': 0, 'error': f'No adc_values, found: {datasets}'}
except Exception as e:
return {'samples': 0, 'error': str(e)}
def format_datetime(epoch_time):
"""Formate un timestamp en date/heure lisible."""
if not epoch_time:
return "N/A"
dt = datetime.fromtimestamp(epoch_time)
return dt.strftime('%Y-%m-%d %H:%M:%S')
def format_duration(seconds):
"""Formate une durée en heures:minutes:secondes."""
hours = int(seconds // 3600)
minutes = int((seconds % 3600) // 60)
secs = int(seconds % 60)
if hours > 0:
return f"{hours}h {minutes}m {secs}s"
elif minutes > 0:
return f"{minutes}m {secs}s"
else:
return f"{secs}s"
def scan_directory(data_dir):
"""Scanne un répertoire pour trouver tous les fichiers HDF5."""
files = []
data_path = Path(data_dir) / 'data'
if not data_path.exists():
print(f" Directory not found: {data_path}")
return files
for filepath in data_path.glob('*.h5'):
files.append(filepath)
return files
def generate_html(inventory, output_path):
"""Génère le document HTML."""
# Organiser par bumper puis par canal
by_bumper = defaultdict(lambda: defaultdict(list))
for item in inventory:
bumper = item['bumper_id'] or 'unknown'
channel = item['channel'] or 'unknown'
by_bumper[bumper][channel].append(item)
# Trier les bumpers numériquement
sorted_bumpers = sorted(by_bumper.keys(), key=lambda x: int(x) if x.isdigit() else 999)
# Statistiques globales
total_files = len(inventory)
total_samples = sum(i['samples'] for i in inventory)
total_duration = total_samples / SAMPLE_RATE
total_errors = sum(1 for i in inventory if i['error'])
# Compter par canal
channel_stats = defaultdict(lambda: {'files': 0, 'samples': 0, 'bumpers': set()})
for item in inventory:
ch = item['channel'] or 'unknown'
channel_stats[ch]['files'] += 1
channel_stats[ch]['samples'] += item['samples']
if item['bumper_id']:
channel_stats[ch]['bumpers'].add(item['bumper_id'])
html = f"""<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Inventaire Fichiers HDF5 Sismiques</title>
<style>
* {{ box-sizing: border-box; }}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #0a0a1a;
color: #eee;
margin: 0;
padding: 20px;
}}
h1 {{
color: #4ade80;
border-bottom: 2px solid #4ade80;
padding-bottom: 10px;
}}
h2 {{
color: #e94560;
margin-top: 30px;
}}
h3 {{
color: #fbbf24;
margin-top: 20px;
}}
.stats {{
background: #16213e;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}}
.stat-box {{
background: #0f3460;
padding: 15px;
border-radius: 6px;
text-align: center;
}}
.stat-value {{
font-size: 2rem;
font-weight: bold;
color: #4ade80;
}}
.stat-label {{
color: #888;
font-size: 0.9rem;
}}
.channel-summary {{
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 30px;
}}
.channel-box {{
background: #16213e;
padding: 15px;
border-radius: 6px;
text-align: center;
}}
.channel-box h4 {{
margin: 0 0 10px 0;
color: #4ade80;
}}
table {{
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
font-size: 0.9rem;
}}
th, td {{
padding: 10px;
text-align: left;
border-bottom: 1px solid #1a1a2e;
}}
th {{
background: #16213e;
color: #4ade80;
position: sticky;
top: 0;
}}
tr:hover {{
background: #16213e;
}}
.ch0 {{ color: #4ade80; }}
.ch1 {{ color: #60a5fa; }}
.ch2 {{ color: #fbbf24; }}
.ch3 {{ color: #f472b6; }}
.data {{ color: #4ade80; }}
.aux {{ color: #888; }}
.error {{ color: #e94560; font-size: 0.8rem; }}
.bumper-section {{
background: #0f3460;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}}
.filter-controls {{
background: #16213e;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
gap: 20px;
flex-wrap: wrap;
}}
.filter-controls label {{
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}}
input[type="checkbox"] {{
width: 18px;
height: 18px;
}}
.summary-table {{
width: auto;
margin: 0 auto;
}}
.summary-table td {{
padding: 5px 15px;
}}
</style>
</head>
<body>
<h1>📊 Inventaire Fichiers HDF5 Sismiques</h1>
<p>Généré le {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<div class="stats">
<div class="stat-box">
<div class="stat-value">{total_files}</div>
<div class="stat-label">Fichiers HDF5</div>
</div>
<div class="stat-box">
<div class="stat-value">{len(sorted_bumpers)}</div>
<div class="stat-label">Bumpers (nodes)</div>
</div>
<div class="stat-box">
<div class="stat-value">{total_samples:,}</div>
<div class="stat-label">Échantillons total</div>
</div>
<div class="stat-box">
<div class="stat-value">{format_duration(total_duration)}</div>
<div class="stat-label">Durée totale @ 200Hz</div>
</div>
<div class="stat-box">
<div class="stat-value">{total_errors}</div>
<div class="stat-label">Erreurs lecture</div>
</div>
</div>
<h2>📡 Résumé par Canal</h2>
<div class="channel-summary">
"""
for ch in ['ch0', 'ch1', 'ch2', 'ch3']:
stats = channel_stats.get(ch, {'files': 0, 'samples': 0, 'bumpers': set()})
duration = stats['samples'] / SAMPLE_RATE
html += f"""
<div class="channel-box">
<h4 class="{ch}">{ch.upper()}</h4>
<div><strong>{stats['files']}</strong> fichiers</div>
<div><strong>{len(stats['bumpers'])}</strong> bumpers</div>
<div><strong>{stats['samples']:,}</strong> samples</div>
<div>{format_duration(duration)}</div>
</div>
"""
html += """
</div>
<h2>📋 Détail par Bumper</h2>
<div class="filter-controls">
<label><input type="checkbox" id="showCh0" checked onchange="filterTable()"> <span class="ch0">CH0</span></label>
<label><input type="checkbox" id="showCh1" checked onchange="filterTable()"> <span class="ch1">CH1</span></label>
<label><input type="checkbox" id="showCh2" checked onchange="filterTable()"> <span class="ch2">CH2</span></label>
<label><input type="checkbox" id="showCh3" checked onchange="filterTable()"> <span class="ch3">CH3</span></label>
<label><input type="checkbox" id="showData" checked onchange="filterTable()"> <span class="data">DATA</span></label>
<label><input type="checkbox" id="showAux" checked onchange="filterTable()"> <span class="aux">AUX</span></label>
</div>
<table id="mainTable">
<thead>
<tr>
<th>Bumper</th>
<th>Canal</th>
<th>Type</th>
<th>Début (epoch)</th>
<th>Début (date/heure)</th>
<th>Fin (date/heure)</th>
<th>Durée</th>
<th>Samples</th>
<th>Fichier</th>
</tr>
</thead>
<tbody>
"""
for bumper in sorted_bumpers:
channels = by_bumper[bumper]
for channel in sorted(channels.keys()):
items = sorted(channels[channel], key=lambda x: x['epoch_time'] or 0)
for item in items:
duration_sec = item['samples'] / SAMPLE_RATE
end_time = (item['epoch_time'] + duration_sec) if item['epoch_time'] else None
error_html = f'<div class="error">{item["error"]}</div>' if item['error'] else ''
html += f"""
<tr class="row-{channel} row-{item['file_type']}">
<td><strong>b{bumper}</strong></td>
<td class="{channel}">{channel.upper()}</td>
<td class="{item['file_type']}">{item['file_type'].upper()}</td>
<td>{item['epoch_time'] or 'N/A'}</td>
<td>{format_datetime(item['epoch_time'])}</td>
<td>{format_datetime(end_time)}</td>
<td>{format_duration(duration_sec)}</td>
<td>{item['samples']:,}</td>
<td style="font-size: 0.8rem; color: #888;">{item['filename']}{error_html}</td>
</tr>
"""
html += """
</tbody>
</table>
<script>
function filterTable() {
const showCh0 = document.getElementById('showCh0').checked;
const showCh1 = document.getElementById('showCh1').checked;
const showCh2 = document.getElementById('showCh2').checked;
const showCh3 = document.getElementById('showCh3').checked;
const showData = document.getElementById('showData').checked;
const showAux = document.getElementById('showAux').checked;
const rows = document.querySelectorAll('#mainTable tbody tr');
rows.forEach(row => {
const isCh0 = row.classList.contains('row-ch0');
const isCh1 = row.classList.contains('row-ch1');
const isCh2 = row.classList.contains('row-ch2');
const isCh3 = row.classList.contains('row-ch3');
const isData = row.classList.contains('row-data');
const isAux = row.classList.contains('row-aux');
const channelVisible = (isCh0 && showCh0) || (isCh1 && showCh1) ||
(isCh2 && showCh2) || (isCh3 && showCh3);
const typeVisible = (isData && showData) || (isAux && showAux);
row.style.display = (channelVisible && typeVisible) ? '' : 'none';
});
}
</script>
</body>
</html>
"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html)
print(f"\nHTML genere: {output_path}")
def main():
print("=" * 60)
print("INVENTAIRE DES FICHIERS HDF5 SISMIQUES")
print("=" * 60)
# Charger l'index existant pour connaître tous les répertoires
index_path = Path(r"F:\seismic_webapp\data\index.json")
all_dirs = set()
if index_path.exists():
with open(index_path, 'r') as f:
index = json.load(f)
# Récupérer tous les répertoires de dates
for node_data in index.get('nodes', {}).values():
for files_list in node_data.get('dates', {}).values():
# files_list est une liste de fichiers directement
if isinstance(files_list, list):
for file_info in files_list:
file_path = Path(file_info.get('path', ''))
if file_path.parent.parent.exists():
all_dirs.add(str(file_path.parent.parent))
# Ajouter les répertoires par défaut
for d in DATA_DIRS:
if Path(d).exists():
all_dirs.add(d)
print(f"\nRépertoires à scanner: {len(all_dirs)}")
for d in sorted(all_dirs):
print(f" - {d}")
# Scanner tous les fichiers
inventory = []
for data_dir in sorted(all_dirs):
print(f"\nScanning {data_dir}...")
files = scan_directory(data_dir)
print(f" Found {len(files)} HDF5 files")
for i, filepath in enumerate(files):
if i % 50 == 0:
print(f" Processing {i}/{len(files)}...")
parsed = parse_filename(filepath.name)
hdf5_info = get_hdf5_info(filepath)
inventory.append({
'filepath': str(filepath),
'filename': filepath.name,
'directory': data_dir,
'bumper_id': parsed['bumper_id'],
'channel': parsed['channel'],
'epoch_time': parsed['epoch_time'],
'file_type': parsed['file_type'],
'samples': hdf5_info['samples'],
'error': hdf5_info['error']
})
print(f"\nTotal: {len(inventory)} fichiers")
# Générer le HTML
output_path = Path(r"F:\seismic_webapp\inventory.html")
generate_html(inventory, output_path)
# Aussi sauvegarder en JSON pour référence
json_path = Path(r"F:\seismic_webapp\inventory.json")
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(inventory, f, indent=2, ensure_ascii=False)
print(f"JSON genere: {json_path}")
if __name__ == '__main__':
main()

125
scripts/h5_api_server.py Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
from flask import Flask, jsonify, request, send_file
from flask_cors import CORS
import h5py
import json
from pathlib import Path
import re
app = Flask(__name__)
CORS(app)
H5_DIR = Path('/home/floppyrj45/docker/seismic-nodes-viewer/data/h5')
DOCS_DIR = Path('/home/floppyrj45/docker/seismic-nodes-viewer/data/docs')
@app.route('/api/h5/files', methods=['GET'])
def list_files():
try:
files = []
for h5_file in sorted(H5_DIR.glob('*.h5')):
match = re.search(r'rsn(\d+)', h5_file.name)
node_id = match.group(1) if match else 'unknown'
match_date = re.search(r'_(\d{6})_', h5_file.name)
date = match_date.group(1) if match_date else ''
files.append({
'filename': h5_file.name,
'nodeId': node_id,
'date': date,
'path': str(h5_file)
})
return jsonify({'files': files, 'count': len(files)})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/h5/data', methods=['GET'])
def get_data():
try:
filename = request.args.get('file')
channel = int(request.args.get('channel', 1))
start = int(request.args.get('start', 0))
duration = int(request.args.get('duration', 10))
filepath = H5_DIR / filename
with h5py.File(filepath, 'r') as f:
meta = f['metadata']
sample_rate = meta.attrs['sample_rate_hz']
file_duration = meta.attrs['duration_sec']
total_samples = meta.attrs['n_samples']
dataset = f[f'calibrated_data/channel_{channel}']
start_idx = int(start * sample_rate) if start > 0 else 0
num_samples = int(duration * sample_rate) if duration > 0 else total_samples
end_idx = min(start_idx + num_samples, total_samples)
samples = dataset[start_idx:end_idx]
unit = 'm/s' if channel in [1, 2, 3] else 'Pa'
channel_name = f'Geophone {channel}' if channel in [1, 2, 3] else 'Hydrophone'
import numpy as np
return jsonify({
'samples': samples.tolist(),
'start_idx': int(start_idx),
'end_idx': int(end_idx),
'total_samples': int(total_samples),
'sample_rate': int(sample_rate),
'duration_sec': float(file_duration),
'channel': channel,
'channel_name': channel_name,
'unit': unit,
'stats': {
'min': float(np.min(samples)),
'max': float(np.max(samples)),
'mean': float(np.mean(samples)),
'std': float(np.std(samples)),
'rms': float(np.sqrt(np.mean(samples**2)))
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/docs/manifest', methods=['GET'])
def get_manifest():
try:
manifest_file = DOCS_DIR / 'campaign_manifest.json'
with open(manifest_file, 'r') as f:
return jsonify(json.load(f))
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/docs/<filename>', methods=['GET'])
def get_document(filename):
try:
doc_file = DOCS_DIR / filename
if not doc_file.exists():
return jsonify({'error': 'File not found'}), 404
return send_file(str(doc_file))
except Exception as e:
return jsonify({'error': str(e)}), 500
# === Endpoints pour la carte ===
@app.route('/api/nodes', methods=['GET'])
def get_nodes():
"""Retourne la liste des nodes avec leurs positions"""
nodes = [
{'id': '80274', 'lat': 43.40, 'lon': 3.70, 'name': 'Node 80274'},
{'id': '2221', 'lat': 43.41, 'lon': 3.71, 'name': 'Node 2221'},
{'id': '3541', 'lat': 43.39, 'lon': 3.69, 'name': 'Node 3541'},
]
return jsonify(nodes)
@app.route('/api/dates', methods=['GET'])
def get_dates():
"""Retourne les dates disponibles"""
return jsonify(['2020-08-08', '2020-08-09', '2020-08-10'])
@app.route('/api/migration-status', methods=['GET'])
def migration_status():
"""Status de migration (désactivé)"""
return jsonify({'status': 'complete'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3004, debug=False)

64
scripts/index_h5_2026.py Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Indexation des fichiers H5 format 2026 avec métadonnées complètes.
Génère un index JSON pour le viewer web.
"""
import h5py
import json
from pathlib import Path
from datetime import datetime
H5_DIR = Path('/home/floppyrj45/docker/seismic-nodes-viewer/data/h5')
OUTPUT = Path('/home/floppyrj45/docker/seismic-nodes-viewer/data/h5_index.json')
def index_h5_files():
files = []
for h5_file in sorted(H5_DIR.glob('*.h5')):
try:
with h5py.File(h5_file, 'r') as f:
meta = f['metadata']
# Extraire node ID du nom de fichier (rsn[0-9]+)
import re
match = re.search(r'rsn(\d+)', h5_file.name)
node_id = match.group(1) if match else 'unknown'
# Extraire date du nom (YYMMDD)
match_date = re.search(r'_(\d{6})_', h5_file.name)
date_str = match_date.group(1) if match_date else ''
files.append({
'filename': h5_file.name,
'path': str(h5_file),
'node_id': node_id,
'date': date_str,
'duration_sec': float(meta.attrs['duration_sec']),
'sample_rate': int(meta.attrs['sample_rate_hz']),
'channels': int(meta.attrs['n_channels']),
'samples': int(meta.attrs['n_samples']),
'size_mb': round(h5_file.stat().st_size / (1024*1024), 2),
'channel_info': [
{'id': 1, 'type': 'geophone', 'unit': 'm/s', 'name': 'Geophone 1'},
{'id': 2, 'type': 'geophone', 'unit': 'm/s', 'name': 'Geophone 2'},
{'id': 3, 'type': 'geophone', 'unit': 'm/s', 'name': 'Geophone 3'},
{'id': 4, 'type': 'hydrophone', 'unit': 'Pa', 'name': 'Hydrophone'}
]
})
except Exception as e:
print(f'Error indexing {h5_file.name}: {e}')
index = {
'generated': datetime.now().isoformat(),
'total_files': len(files),
'total_duration_hours': sum(f['duration_sec'] for f in files) / 3600,
'files': files
}
OUTPUT.write_text(json.dumps(index, indent=2))
print(f'✅ Indexed {len(files)} files → {OUTPUT}')
print(f'📊 Total duration: {index["total_duration_hours"]:.1f} hours')
if __name__ == '__main__':
index_h5_files()

231
scripts/index_h5_files.py Executable file
View File

@@ -0,0 +1,231 @@
"""
Script d'indexation des fichiers HDF5 sismiques.
Parcourt les dossiers de données, extrait les métadonnées (node_id, date, canaux)
et génère un index JSON utilisé par l'API backend.
"""
import os
import re
import json
import csv
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any
# Pattern pour extraire les infos du nom de fichier
# Exemple: auto_256_070617_b67_14_025708_data_rsn6027_seq1_ch0_1599057453.h5
# ou: auto_255_125334_b4_rsn13696_seq1_1599045513.h5
FILENAME_PATTERN = re.compile(
r'auto_(\d+)_(\d{6})_b(\d+).*?_(\d{10})\.h5$',
re.IGNORECASE
)
# Dossiers racine contenant les données H5
DATA_ROOTS = [
Path(r"F:\2020-09-12"),
Path(r"F:\2020-09-13"),
Path(r"F:\2020-09-14"),
Path(r"F:\2020-09-15"),
Path(r"F:\2020-09-16"),
Path(r"F:\2020-09-17"),
Path(r"F:\2020-09-18"),
Path(r"F:\2020-09-19"),
Path(r"F:\2020-09-21"),
Path(r"F:\2020-09-22"),
Path(r"F:\2020-09-23"),
]
# Fichier CSV des positions
POSITIONS_CSV = Path(r"F:\Copie de SETE_AUV_DARFV4-Copier(1).csv")
# Sortie
OUTPUT_INDEX = Path(r"F:\seismic_webapp\data\index.json")
def load_node_positions(csv_path: Path) -> Dict[str, Dict[str, Any]]:
"""
Charge les positions des nodes depuis le CSV.
Retourne un dict: node_id -> {easting, northing, depth, ...}
"""
positions = {}
with open(csv_path, 'r', encoding='utf-8', errors='replace') as f:
# Sauter les premières lignes d'en-tête (lignes 1-4)
lines = f.readlines()
# La ligne 4 (index 3) contient les vrais en-têtes
if len(lines) < 5:
return positions
header_line = lines[3]
headers = header_line.strip().split(',')
# Trouver les indices des colonnes importantes
# Utiliser Aslaid (positions réelles mesurées) plutôt que Preplot (planifiées)
try:
node_code_idx = headers.index('NodeCode')
# Priorité aux positions Aslaid (réelles), sinon Preplot (planifiées)
if 'Aslaid Easting' in headers:
easting_idx = headers.index('Aslaid Easting')
northing_idx = headers.index('Aslaid Northing')
depth_idx = headers.index('Aslaid Depth') if 'Aslaid Depth' in headers else None
print("Utilisation des coordonnées Aslaid (positions réelles)")
else:
easting_idx = headers.index('Preplot Easting')
northing_idx = headers.index('Preplot Northing')
depth_idx = headers.index('Preplot Depth') if 'Preplot Depth' in headers else None
print("Utilisation des coordonnées Preplot (positions planifiées)")
except ValueError as e:
print(f"Colonne manquante dans le CSV: {e}")
# Fallback sur indices connus (Aslaid)
node_code_idx = 3
easting_idx = 9 # Aslaid Easting
northing_idx = 10 # Aslaid Northing
depth_idx = 11 # Aslaid Depth
# Parser les lignes de données (à partir de la ligne 5)
for line in lines[4:]:
parts = line.strip().split(',')
if len(parts) <= max(node_code_idx, easting_idx, northing_idx):
continue
node_code = parts[node_code_idx].strip()
if not node_code or node_code == '':
continue
try:
easting = float(parts[easting_idx]) if parts[easting_idx] else None
northing = float(parts[northing_idx]) if parts[northing_idx] else None
depth = float(parts[depth_idx]) if depth_idx and parts[depth_idx] else 0.0
except (ValueError, IndexError):
continue
if easting and northing:
positions[node_code] = {
'easting': easting,
'northing': northing,
'depth': depth,
}
print(f"Chargé {len(positions)} positions de nodes")
return positions
def scan_h5_files(data_roots: List[Path]) -> Dict[str, Any]:
"""
Parcourt les dossiers et indexe tous les fichiers H5.
Retourne un dict structuré par node_id -> date -> fichiers
"""
index = {}
file_count = 0
for root in data_roots:
if not root.exists():
print(f"Dossier non trouvé: {root}")
continue
print(f"Scan de {root}...")
for h5_file in root.rglob("*.h5"):
match = FILENAME_PATTERN.search(h5_file.name)
if not match:
# Essayer un pattern plus simple
simple_match = re.search(r'_b(\d+)_.*?(\d{10})\.h5$', h5_file.name, re.IGNORECASE)
if simple_match:
node_id = simple_match.group(1)
timestamp = int(simple_match.group(2))
else:
continue
else:
node_id = match.group(3)
timestamp = int(match.group(4))
# Convertir timestamp en date
dt = datetime.fromtimestamp(timestamp)
date_str = dt.strftime('%Y-%m-%d')
# Détecter les canaux disponibles dans le fichier
# Pour l'instant on suppose ch0-ch3 par défaut
channels = ['ch0', 'ch1', 'ch2', 'ch3']
# Structure: node_id -> date -> liste de fichiers
if node_id not in index:
index[node_id] = {}
if date_str not in index[node_id]:
index[node_id][date_str] = []
index[node_id][date_str].append({
'path': str(h5_file),
'timestamp': timestamp,
'channels': channels,
'size_bytes': h5_file.stat().st_size if h5_file.exists() else 0
})
file_count += 1
print(f"Indexé {file_count} fichiers H5")
return index
def build_full_index(positions: Dict, files_index: Dict) -> Dict[str, Any]:
"""
Combine les positions et l'index des fichiers.
"""
full_index = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': 200,
'nodes': {},
'dates': set(),
}
# Fusionner les données
all_node_ids = set(files_index.keys()) | set(positions.keys())
for node_id in all_node_ids:
node_data = {
'id': node_id,
'position': positions.get(node_id, None),
'dates': {}
}
if node_id in files_index:
node_data['dates'] = files_index[node_id]
for date_str in files_index[node_id].keys():
full_index['dates'].add(date_str)
full_index['nodes'][node_id] = node_data
# Convertir le set en liste triée
full_index['dates'] = sorted(list(full_index['dates']))
return full_index
def main():
print("=== Indexation des fichiers HDF5 sismiques ===\n")
# 1. Charger les positions
print("1. Chargement des positions des nodes...")
positions = load_node_positions(POSITIONS_CSV)
# 2. Scanner les fichiers H5
print("\n2. Scan des fichiers H5...")
files_index = scan_h5_files(DATA_ROOTS)
# 3. Construire l'index complet
print("\n3. Construction de l'index...")
full_index = build_full_index(positions, files_index)
# 4. Sauvegarder
print(f"\n4. Sauvegarde vers {OUTPUT_INDEX}...")
OUTPUT_INDEX.parent.mkdir(parents=True, exist_ok=True)
with open(OUTPUT_INDEX, 'w', encoding='utf-8') as f:
json.dump(full_index, f, indent=2, ensure_ascii=False)
print(f"\nTerminé! Index généré avec {len(full_index['nodes'])} nodes et {len(full_index['dates'])} dates.")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,87 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
di = headers.index('Aslaid Depth') if 'Aslaid Depth' in headers else -1
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[di]) if di != -1 else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
index = {}
file_count = 0
print(f"Scanning H5 files... Positions loaded: {len(pos)}")
all_files = []
for root in DATA_ROOTS:
all_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_files):
try:
match = re.search(r'_b(\d+)_', h5_path.name)
if not match: continue
nid = match.group(1)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
ds = f['adc_values']
start_ts = int(ds.attrs.get('timestamp', 0))
if start_ts == 0: continue
duration = ds.shape[0] / SAMPLE_RATE
end_ts = start_ts + duration
if nid not in index:
index[nid] = {
'id': nid,
'position': pos.get(nid),
'files': []
}
index[nid]['files'].append({
'path': str(h5_path),
'start': start_ts,
'end': end_ts,
'channels': ['ch0', 'ch1', 'ch2', 'ch3']
})
file_count += 1
except: continue
# Sauvegarder l'index
with open(OUTPUT_INDEX, 'w') as f:
json.dump({
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': index
}, f)
print(f"Index généré: {file_count} fichiers, {len(index)} nodes avec positions.")
if __name__ == '__main__': scan()

105
scripts/index_ultimate.py Normal file
View File

@@ -0,0 +1,105 @@
import os, re, json, h5py
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
DATA_ROOTS = [Path("/mnt/kingston"), Path("/mnt/data_sdb1")]
POSITIONS_CSV = Path("/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv")
OUTPUT_INDEX = Path("/mnt/kingston/seismic_webapp/data/index.json")
SAMPLE_RATE = 200
def load_pos():
positions = {}
if not POSITIONS_CSV.exists(): return {}
with open(POSITIONS_CSV, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
if len(lines) < 5: return {}
headers = lines[3].strip().split(',')
try:
ni = headers.index('NodeCode')
ei = headers.index('Aslaid Easting') if 'Aslaid Easting' in headers else headers.index('Preplot Easting')
oi = headers.index('Aslaid Northing') if 'Aslaid Northing' in headers else headers.index('Preplot Northing')
except: return {}
for line in lines[4:]:
parts = line.strip().split(',')
try:
nid = parts[ni].strip()
positions[nid] = {
'easting': float(parts[ei]),
'northing': float(parts[oi]),
'depth': float(parts[headers.index('Aslaid Depth')]) if 'Aslaid Depth' in headers else 0.0
}
except: continue
return positions
def scan():
pos = load_pos()
nodes = {}
all_dates = set()
file_count = 0
print("🔍 Scanning ONLY 'data' H5 files (ignoring 'aux')...")
all_h5_files = []
for root in DATA_ROOTS:
all_h5_files.extend(list(root.rglob("*.h5")))
for h5_path in tqdm(all_h5_files):
# FILTRE : Uniquement les fichiers contenant "data"
if "_data_" not in h5_path.name.lower():
continue
try:
match = re.search(r'auto_(\d+)_(\d{6})_b(\d+)_.*?_(\d{10})\.h5$', h5_path.name)
if not match: continue
julian_day = int(match.group(1))
time_str = match.group(2)
node_id = match.group(3)
date_ref = datetime(2020, 1, 1) + timedelta(days=julian_day - 1)
date_str = date_ref.strftime('%Y-%m-%d')
h, m, s = int(time_str[:2]), int(time_str[2:4]), int(time_str[4:6])
actual_start_ts = int(datetime(2020, 1, 1).timestamp() + (julian_day - 1) * 86400 + h * 3600 + m * 60 + s)
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f: continue
duration = f['adc_values'].shape[0] / SAMPLE_RATE
actual_end_ts = actual_start_ts + duration
all_dates.add(date_str)
if node_id not in nodes:
nodes[node_id] = {
'id': node_id,
'position': pos.get(node_id),
'files': []
}
# On extrait le canal du nom de fichier pour un matching plus précis
channel_match = re.search(r'_ch(\d+)_', h5_path.name)
channel = f"ch{channel_match.group(1)}" if channel_match else "ch0"
nodes[node_id]['files'].append({
'path': str(h5_path),
'start': actual_start_ts,
'end': actual_end_ts,
'julian': julian_day,
'channel': channel # Canal spécifique au fichier
})
file_count += 1
except: continue
result = {
'generated_at': datetime.now().isoformat(),
'sample_rate_hz': SAMPLE_RATE,
'nodes': nodes,
'dates': sorted(list(all_dates))
}
with open(OUTPUT_INDEX, 'w') as f:
json.dump(result, f, indent=2)
print(f"✅ Index updated: {file_count} 'data' files, {len(nodes)} nodes.")
if __name__ == '__main__': scan()

158
scripts/inventory_h5.py Executable file
View File

@@ -0,0 +1,158 @@
"""
Script d'inventaire des fichiers HDF5.
Extrait les timestamps des noms de fichiers et génère un rapport.
"""
import os
import re
from pathlib import Path
from datetime import datetime
from collections import defaultdict
# Dossiers racine
DATA_ROOTS = [
Path(r"F:\2020-09-12"),
Path(r"F:\2020-09-13"),
Path(r"F:\2020-09-14"),
Path(r"F:\2020-09-15"),
Path(r"F:\2020-09-16"),
Path(r"F:\2020-09-17"),
Path(r"F:\2020-09-18"),
Path(r"F:\2020-09-19"),
Path(r"F:\2020-09-21"),
Path(r"F:\2020-09-22"),
Path(r"F:\2020-09-23"),
]
# Pattern pour extraire node_id et timestamp
# Exemple: auto_256_070617_b67_14_025708_data_rsn6027_seq1_ch0_1599057453.h5
PATTERN = re.compile(r'_b(\d+)_.*?(\d{10})\.h5$', re.IGNORECASE)
def main():
print("=" * 70)
print("INVENTAIRE DES FICHIERS HDF5")
print("=" * 70)
# Structure: folder -> node_id -> list of (timestamp, filename, type)
inventory = defaultdict(lambda: defaultdict(list))
# Stats globales
total_files = 0
total_size = 0
nodes_set = set()
timestamps_set = set()
for root in DATA_ROOTS:
if not root.exists():
continue
folder_name = root.name
for h5_file in root.rglob("*.h5"):
match = PATTERN.search(h5_file.name)
if not match:
continue
node_id = match.group(1)
timestamp = int(match.group(2))
# Déterminer le type (data ou aux)
file_type = "data" if "_data_" in h5_file.name else "aux" if "_aux_" in h5_file.name else "unknown"
# Extraire le channel si présent
ch_match = re.search(r'_ch(\d+)_', h5_file.name)
channel = f"ch{ch_match.group(1)}" if ch_match else "?"
file_size = h5_file.stat().st_size
inventory[folder_name][node_id].append({
'timestamp': timestamp,
'datetime': datetime.fromtimestamp(timestamp),
'type': file_type,
'channel': channel,
'filename': h5_file.name,
'size': file_size
})
total_files += 1
total_size += file_size
nodes_set.add(node_id)
timestamps_set.add(timestamp)
# Rapport par dossier
print(f"\n{'DOSSIER':<15} {'NODES':<10} {'FICHIERS':<10} {'TAILLE':<15}")
print("-" * 50)
for folder in sorted(inventory.keys()):
folder_data = inventory[folder]
n_nodes = len(folder_data)
n_files = sum(len(files) for files in folder_data.values())
folder_size = sum(f['size'] for files in folder_data.values() for f in files)
print(f"{folder:<15} {n_nodes:<10} {n_files:<10} {folder_size / 1e9:.2f} GB")
# Stats globales
print("\n" + "=" * 70)
print("STATISTIQUES GLOBALES")
print("=" * 70)
print(f"Fichiers H5 totaux: {total_files}")
print(f"Taille totale: {total_size / 1e9:.2f} GB")
print(f"Nodes uniques: {len(nodes_set)}")
# Plage temporelle
if timestamps_set:
min_ts = min(timestamps_set)
max_ts = max(timestamps_set)
print(f"\nPlage temporelle des données:")
print(f" Début: {datetime.fromtimestamp(min_ts)} (timestamp: {min_ts})")
print(f" Fin: {datetime.fromtimestamp(max_ts)} (timestamp: {max_ts})")
# Détail par node (top 20)
print("\n" + "=" * 70)
print("DETAIL PAR NODE (nodes avec le plus de fichiers)")
print("=" * 70)
# Agréger par node
node_stats = defaultdict(lambda: {'files': 0, 'size': 0, 'timestamps': set(), 'folders': set()})
for folder, folder_data in inventory.items():
for node_id, files in folder_data.items():
node_stats[node_id]['files'] += len(files)
node_stats[node_id]['size'] += sum(f['size'] for f in files)
node_stats[node_id]['timestamps'].update(f['timestamp'] for f in files)
node_stats[node_id]['folders'].add(folder)
# Trier par nombre de fichiers
sorted_nodes = sorted(node_stats.items(), key=lambda x: x[1]['files'], reverse=True)
print(f"\n{'NODE':<8} {'FICHIERS':<10} {'TAILLE':<12} {'DATES':<25} {'DOSSIERS'}")
print("-" * 90)
for node_id, stats in sorted_nodes[:30]:
ts_list = sorted(stats['timestamps'])
if ts_list:
date_range = f"{datetime.fromtimestamp(ts_list[0]).strftime('%Y-%m-%d %H:%M')} -> {datetime.fromtimestamp(ts_list[-1]).strftime('%H:%M')}"
else:
date_range = "N/A"
folders = ", ".join(sorted(stats['folders']))
print(f"b{node_id:<7} {stats['files']:<10} {stats['size']/1e6:.1f} MB {date_range:<25} {folders}")
# Dates uniques (jours)
print("\n" + "=" * 70)
print("JOURS DE DONNEES DISPONIBLES (basé sur timestamps)")
print("=" * 70)
days = set()
for ts in timestamps_set:
days.add(datetime.fromtimestamp(ts).strftime('%Y-%m-%d'))
for day in sorted(days):
# Compter les fichiers pour ce jour
day_files = sum(1 for ts in timestamps_set
if datetime.fromtimestamp(ts).strftime('%Y-%m-%d') == day)
print(f" {day}: ~{day_files} timestamps uniques")
if __name__ == '__main__':
main()

45
scripts/migrate_all.py Normal file
View File

@@ -0,0 +1,45 @@
import json, psycopg2, os
from pathlib import Path
from migrate_to_db import migrate_file
INDEX_PATH = "/mnt/kingston/seismic_webapp/data/index.json"
DB_URL = "postgresql://postgres:seismic_pass@db:5432/seismic_data"
def update_status(processed, total, current):
try:
conn = psycopg2.connect(DB_URL)
cur = conn.cursor()
cur.execute("UPDATE migration_status SET processed_files = %s, total_files = %s, current_file = %s, last_update = NOW() WHERE id = 1", (processed, total, current))
conn.commit()
cur.close()
conn.close()
except Exception as e:
print(f"Status update error: {e}")
def main():
with open(INDEX_PATH, 'r') as f:
index = json.load(f)
nodes = index.get('nodes', {})
all_files = []
for nid, node in nodes.items():
for f in node.get('files', []):
if '_data_' in f['path']:
all_files.append((nid, f))
total = len(all_files)
print(f"Starting migration for {total} files...")
for i, (nid, f) in enumerate(all_files):
filename = os.path.basename(f['path'])
update_status(i, total, filename)
try:
# Migration de 1h de chaque fichier
migrate_file(f['path'], nid, f.get('channel', 'ch0'), duration_sec=3600)
except Exception as e:
print(f"Error migrating {filename}: {e}")
update_status(total, total, "Terminé")
if __name__ == "__main__":
main()

53
scripts/migrate_to_db.py Normal file
View File

@@ -0,0 +1,53 @@
import h5py
import numpy as np
import psycopg2
from psycopg2.extras import execute_values
from datetime import datetime, timezone, timedelta
import os
from tqdm import tqdm
DB_URL = "postgresql://postgres:seismic_pass@db:5432/seismic_data"
def fix_path(p):
p = p.replace('\\', '/')
if p.startswith('F:/'): return '/mnt/kingston/' + p[3:]
if p.startswith('E:/'): return '/mnt/data_sdb1/' + p[3:]
return p
def migrate_file(h5_path, node_id, channel, start_offset_sec=0, duration_sec=3600):
h5_path = fix_path(h5_path)
conn = psycopg2.connect(DB_URL)
cur = conn.cursor()
with h5py.File(h5_path, 'r') as f:
ds = f['adc_values']
start_ts = int(ds.attrs['timestamp'])
# On calcule le début réel
actual_start = start_ts + start_offset_sec
start_idx = start_offset_sec * 200
end_idx = start_idx + (duration_sec * 200)
data = ds[start_idx:end_idx]
print(f"Migrating {len(data)} samples...")
# Préparation des tuples pour insertion par lots
batch_size = 10000
for i in range(0, len(data), batch_size):
batch = data[i:i+batch_size]
values = []
for j, val in enumerate(batch):
ts = datetime.fromtimestamp(actual_start + (i + j) / 200, tz=timezone.utc)
values.append((ts, node_id, channel, float(val)))
execute_values(cur, "INSERT INTO adc_samples (time, node_id, channel, value) VALUES %s", values)
conn.commit()
cur.close()
conn.close()
print("Done.")
if __name__ == "__main__":
# Test sur Node 193, 1er septembre (Julian 245), 10 minutes
# On cherche un fichier du node 193
import sys
migrate_file(sys.argv[1], sys.argv[2], sys.argv[3], duration_sec=600)

62
scripts/precompute_all.py Normal file
View File

@@ -0,0 +1,62 @@
import json, sys, os, numpy as np, h5py, re
from pathlib import Path
from datetime import datetime, timedelta
from tqdm import tqdm
INDEX_PATH = Path("/mnt/kingston/seismic_webapp/data/index.json")
OUTPUT_DIR = Path("/mnt/kingston/seismic_webapp/data/rms_cache")
SAMPLE_RATE = 200
def fix_path(p):
p = p.replace('\\', '/')
if p.startswith('F:/'): return '/mnt/kingston/' + p[3:]
if p.startswith('E:/'): return '/mnt/data_sdb1/' + p[3:]
return p
def compute_rms(h5_path):
h5_path = fix_path(h5_path)
if not os.path.exists(h5_path): return None
try:
# Extraire le timestamp réel du nom de fichier (Julian day)
match = re.search(r'auto_(\d+)_(\d{6})_b', os.path.basename(h5_path))
if not match: return None
julian, time_str = int(match.group(1)), match.group(2)
h, m, s = int(time_str[:2]), int(time_str[2:4]), int(time_str[4:6])
start_ts = int(datetime(2020, 1, 1).timestamp() + (julian - 1) * 86400 + h * 3600 + m * 60 + s)
with h5py.File(h5_path, 'r') as f:
ds = f['adc_values']
samples = ds[0:5000]
rms = float(np.sqrt(np.mean(samples.astype(np.float64)**2)))
return [{'ts': start_ts, 'rms': rms}]
except: return None
def main():
with open(INDEX_PATH, 'r') as f: index = json.load(f)
nodes = index.get('nodes', {})
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
for date in index['dates']:
channel = "ch0"
output_file = OUTPUT_DIR / f"rms_{{date}}_{{channel}}.json"
print(f"Processing {date}...")
results = {}
for nid, node in tqdm(nodes.items(), desc=f"Nodes {date}"):
files = node.get('files', [])
# Filtrer les fichiers par Julian Day correspondant à la date
dt = datetime.strptime(date, '%Y-%m-%d')
target_julian = dt.timetuple().tm_yday
target = next((f for f in files if f['julian'] == target_julian and f'_{{channel}}_' in f['path']), None)
if not target and files:
target = next((f for f in files if f['julian'] == target_julian), None)
if target:
data = compute_rms(target['path'])
if data: results[nid] = data
if results:
with open(output_file, 'w') as f:
json.dump({'date':date, 'channel':channel, 'nodes':results}, f)
if __name__ == '__main__': main()

189
scripts/precompute_rms.py Executable file
View File

@@ -0,0 +1,189 @@
"""
Pré-calcul des valeurs RMS ADC pour tous les nodes.
Génère un fichier JSON avec les RMS à intervalles réguliers pour une lecture rapide.
"""
import json
import sys
from pathlib import Path
from datetime import datetime
from typing import Dict, List, Any
import numpy as np
import h5py
from tqdm import tqdm
# Configuration
SAMPLE_RATE = 200 # Hz
RMS_INTERVAL_SEC = 60 # Calculer RMS toutes les 60 secondes (plus rapide)
RMS_WINDOW_SEC = 5 # Fenêtre de calcul RMS (5 secondes = 1000 samples)
INDEX_PATH = Path(r"F:\seismic_webapp\data\index.json")
OUTPUT_DIR = Path(r"F:\seismic_webapp\data\rms_cache")
def compute_rms_for_file(h5_path: str, interval_sec: int = RMS_INTERVAL_SEC, window_sec: int = RMS_WINDOW_SEC, max_duration_sec: int = 3600) -> List[Dict]:
"""
Calcule les valeurs RMS à intervalles réguliers pour un fichier HDF5.
Retourne une liste de {timestamp, rms}
max_duration_sec: Limite à traiter (en secondes) pour accélérer
"""
results = []
try:
with h5py.File(h5_path, 'r') as f:
if 'adc_values' not in f:
return results
dataset = f['adc_values']
total_samples = dataset.shape[0]
# Récupérer le timestamp de début
start_ts = None
if 'timestamp' in dataset.attrs:
start_ts = int(dataset.attrs['timestamp'])
if start_ts is None:
return results
# Calculer RMS à intervalles réguliers
window_samples = window_sec * SAMPLE_RATE
interval_samples = interval_sec * SAMPLE_RATE
# Limiter la durée pour accélérer
max_samples = min(total_samples, max_duration_sec * SAMPLE_RATE)
for idx in range(0, max_samples - window_samples, interval_samples):
# Lire uniquement la fenêtre nécessaire
samples = dataset[idx:idx + window_samples]
# Calculer RMS
rms = float(np.sqrt(np.mean(samples.astype(np.float64) ** 2)))
# Timestamp pour ce point
ts = start_ts + (idx // SAMPLE_RATE)
results.append({
'ts': ts,
'rms': rms
})
except Exception as e:
print(f"Erreur lecture {h5_path}: {e}")
return results
def precompute_for_date(index: Dict, date: str, channel: str = 'ch0') -> Dict[str, List[Dict]]:
"""
Pré-calcule les RMS pour tous les nodes pour une date donnée.
Retourne {node_id: [{ts, rms}, ...]}
"""
results = {}
# Trouver tous les nodes avec données pour cette date
nodes_with_data = []
for node_id, node in index['nodes'].items():
if node.get('dates') and date in node['dates']:
nodes_with_data.append((node_id, node['dates'][date]))
print(f"Traitement de {len(nodes_with_data)} nodes pour {date}, canal {channel}")
for node_id, files in tqdm(nodes_with_data, desc=f"Date {date}"):
# Trouver le fichier pour le canal demandé (priorité aux fichiers "data")
channel_pattern = f'_{channel}_'
target_file = None
for f in files:
if channel_pattern in f['path'] and '_data_' in f['path']:
target_file = f
break
if not target_file:
for f in files:
if channel_pattern in f['path']:
target_file = f
break
if not target_file:
continue
# Calculer les RMS
rms_data = compute_rms_for_file(target_file['path'])
if rms_data:
results[node_id] = rms_data
return results
def main():
import argparse
parser = argparse.ArgumentParser(description='Pré-calcul des RMS ADC')
parser.add_argument('--date', help='Date spécifique (ex: 2020-09-02)')
parser.add_argument('--channel', default='ch0', help='Canal (ch0-ch3)')
parser.add_argument('--all', action='store_true', help='Traiter toutes les dates/canaux')
args = parser.parse_args()
# Charger l'index
if not INDEX_PATH.exists():
print(f"Index non trouvé: {INDEX_PATH}")
sys.exit(1)
with open(INDEX_PATH, 'r') as f:
index = json.load(f)
print(f"Index chargé: {len(index['nodes'])} nodes, {len(index['dates'])} dates")
# Créer le dossier de sortie
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# Déterminer quoi traiter
if args.date:
dates_to_process = [args.date]
channels_to_process = [args.channel]
elif args.all:
dates_to_process = index['dates']
channels_to_process = ['ch0', 'ch1', 'ch2', 'ch3']
else:
# Par défaut, traiter la première date disponible, canal ch0
dates_to_process = [index['dates'][0]] if index['dates'] else []
channels_to_process = ['ch0']
for date in dates_to_process:
for channel in channels_to_process:
output_file = OUTPUT_DIR / f"rms_{date}_{channel}.json"
# Skip si déjà calculé
if output_file.exists():
print(f"Skip {output_file.name} (déjà existant)")
continue
print(f"\n=== Traitement {date} - {channel} ===")
results = precompute_for_date(index, date, channel)
if results:
# Sauvegarder
output_data = {
'date': date,
'channel': channel,
'interval_sec': RMS_INTERVAL_SEC,
'window_sec': RMS_WINDOW_SEC,
'nodes': results,
'generated_at': datetime.now().isoformat()
}
with open(output_file, 'w') as f:
json.dump(output_data, f)
print(f"Sauvegardé: {output_file.name} ({len(results)} nodes)")
else:
print(f"Aucune donnée pour {date} - {channel}")
print("\n=== Terminé ===")
if __name__ == '__main__':
main()

83
scripts/rebuild_h5_db.py Executable file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Rebuild H5 metadata database for the seismic viewer."""
import os
import re
import sqlite3
from datetime import datetime
H5_ROOTS = [
'/mnt/data_sdb1',
'/mnt/kingston'
]
DB_PATH = '/home/floppyrj45/docker/seismic-nodes-viewer/h5_data.db'
FILE_PATTERN = re.compile(r'b(\d+)_.*_ch(\d+)')
SCHEMA = [
'CREATE TABLE IF NOT EXISTS positions (node_code INTEGER PRIMARY KEY, has_data BOOLEAN, has_aux BOOLEAN, sample_count INTEGER, last_seen TEXT)',
'CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT, node_code INTEGER, channel INTEGER, dataset TEXT, size INTEGER, mtime INTEGER, FOREIGN KEY(node_code) REFERENCES positions(node_code))',
'CREATE INDEX IF NOT EXISTS idx_files_node ON files(node_code)'
]
def rebuild_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
for stmt in SCHEMA:
cur.execute(stmt)
cur.execute('DELETE FROM files')
cur.execute('DELETE FROM positions')
files_counter = 0
summary = {}
for root in H5_ROOTS:
for dirpath, _, filenames in os.walk(root):
for filename in filenames:
if not filename.endswith('.h5'):
continue
filepath = os.path.join(dirpath, filename)
match = FILE_PATTERN.search(filename)
if not match:
continue
node_code = int(match.group(1))
channel = int(match.group(2))
dataset = 'aux' if 'aux' in filename else 'data'
stat = os.stat(filepath)
mtime = int(stat.st_mtime)
size = stat.st_size
summary.setdefault(node_code, {'data': False, 'aux': False, 'count': 0, 'last': 0})
summary[node_code]['count'] += 1
summary[node_code]['last'] = max(summary[node_code]['last'], mtime)
if dataset == 'data':
summary[node_code]['data'] = True
else:
summary[node_code]['aux'] = True
cur.execute(
'INSERT INTO files (path, node_code, channel, dataset, size, mtime) VALUES (?, ?, ?, ?, ?, ?)',
(filepath, node_code, channel, dataset, size, mtime)
)
files_counter += 1
print(f"Indexed {files_counter} H5 files")
for node_code, stats in summary.items():
has_data = 1 if stats['data'] else 0
has_aux = 1 if stats['aux'] else 0
last_seen = datetime.utcfromtimestamp(stats['last']).isoformat()
cur.execute(
'INSERT INTO positions (node_code, has_data, has_aux, sample_count, last_seen) VALUES (?, ?, ?, ?, ?)',
(node_code, has_data, has_aux, stats['count'], last_seen)
)
conn.commit()
conn.close()
print(f"Rebuilt DB at {DB_PATH} with {len(summary)} positions")
if __name__ == '__main__':
rebuild_db()

104
scripts/rebuild_h5_db_v2.py Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
"""Rebuild H5 metadata database - V2 (capture ALL patterns)."""
import os
import re
import sqlite3
from datetime import datetime
H5_ROOTS = ['/mnt/data_sdb1', '/mnt/kingston']
DB_PATH = '/home/floppyrj45/docker/seismic-nodes-viewer/h5_data.db'
# Pattern plus permissif - capture TOUS les b###
FILE_PATTERN = re.compile(r'b(\d+)')
CHANNEL_PATTERN = re.compile(r'ch(\d+)')
SCHEMA = [
'CREATE TABLE IF NOT EXISTS positions (node_code INTEGER PRIMARY KEY, has_data BOOLEAN, has_aux BOOLEAN, sample_count INTEGER, last_seen TEXT)',
'CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT, node_code INTEGER, channel INTEGER, dataset TEXT, size INTEGER, mtime INTEGER, FOREIGN KEY(node_code) REFERENCES positions(node_code))',
'CREATE INDEX IF NOT EXISTS idx_files_node ON files(node_code)'
]
def rebuild_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
for stmt in SCHEMA:
cur.execute(stmt)
cur.execute('DELETE FROM files')
cur.execute('DELETE FROM positions')
files_counter = 0
summary = {}
for root in H5_ROOTS:
for dirpath, _, filenames in os.walk(root):
for filename in filenames:
if not filename.endswith('.h5'):
continue
filepath = os.path.join(dirpath, filename)
# Extraire node_code
node_match = FILE_PATTERN.search(filename)
if not node_match:
continue
node_code = int(node_match.group(1))
# Extraire channel (peut ne pas exister)
channel_match = CHANNEL_PATTERN.search(filename)
channel = int(channel_match.group(1)) if channel_match else -1
# Déterminer dataset (data vs aux)
dataset = 'aux' if 'aux' in filename else 'data'
stat = os.stat(filepath)
mtime = int(stat.st_mtime)
size = stat.st_size
# Mise à jour summary
summary.setdefault(node_code, {'data': False, 'aux': False, 'count': 0, 'last': 0})
summary[node_code]['count'] += 1
summary[node_code]['last'] = max(summary[node_code]['last'], mtime)
if dataset == 'data':
summary[node_code]['data'] = True
else:
summary[node_code]['aux'] = True
# Insertion fichier
cur.execute(
'INSERT INTO files (path, node_code, channel, dataset, size, mtime) VALUES (?, ?, ?, ?, ?, ?)',
(filepath, node_code, channel, dataset, size, mtime)
)
files_counter += 1
print(f"✓ Indexed {files_counter} H5 files")
# Insertion positions
for node_code, stats in summary.items():
has_data = 1 if stats['data'] else 0
has_aux = 1 if stats['aux'] else 0
last_seen = datetime.fromtimestamp(stats['last']).isoformat()
cur.execute(
'INSERT INTO positions (node_code, has_data, has_aux, sample_count, last_seen) VALUES (?, ?, ?, ?, ?)',
(node_code, has_data, has_aux, stats['count'], last_seen)
)
conn.commit()
# Stats finales
total_positions = len(summary)
with_data = sum(1 for s in summary.values() if s['data'])
with_aux = sum(1 for s in summary.values() if s['aux'])
print(f"✓ Rebuilt DB: {total_positions} positions total")
print(f" • With data files: {with_data}")
print(f" • With aux files: {with_aux}")
print(f" • Both: {sum(1 for s in summary.values() if s['data'] and s['aux'])}")
print(f" • Coverage: {(with_data/205*100):.1f}% (assuming 205 planned)")
conn.close()
if __name__ == '__main__':
rebuild_db()

137
scripts/rebuild_h5_db_v3.py Executable file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""Rebuild H5 metadata database - V3 (include expected positions from CSV)."""
import os
import re
import csv
import sqlite3
from datetime import datetime
H5_ROOTS = ['/mnt/data_sdb1', '/mnt/kingston']
CSV_PATH = '/mnt/kingston/Copie de SETE_AUV_DARFV4-Copier(1).csv'
DB_PATH = '/home/floppyrj45/docker/seismic-nodes-viewer/h5_data.db'
FILE_PATTERN = re.compile(r'b(\d+)')
CHANNEL_PATTERN = re.compile(r'ch(\d+)')
SCHEMA = [
'CREATE TABLE IF NOT EXISTS positions (node_code INTEGER PRIMARY KEY, has_data BOOLEAN, has_aux BOOLEAN, sample_count INTEGER, last_seen TEXT, expected BOOLEAN)',
'CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT, node_code INTEGER, channel INTEGER, dataset TEXT, size INTEGER, mtime INTEGER, FOREIGN KEY(node_code) REFERENCES positions(node_code))',
'CREATE INDEX IF NOT EXISTS idx_files_node ON files(node_code)'
]
def rebuild_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
for stmt in SCHEMA:
cur.execute(stmt)
cur.execute('DELETE FROM files')
cur.execute('DELETE FROM positions')
# 1. Charger les positions attendues depuis le CSV
expected_nodes = set()
try:
with open(CSV_PATH, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
node_code = row.get('NodeCode', '').strip()
if node_code and node_code.isdigit():
expected_nodes.add(int(node_code))
print(f"✓ Loaded {len(expected_nodes)} expected positions from CSV")
except Exception as e:
print(f"⚠ CSV not found or error: {e}")
print(" Continuing with file scan only...")
# 2. Scanner les fichiers H5
files_counter = 0
found_nodes = {}
for root in H5_ROOTS:
for dirpath, _, filenames in os.walk(root):
for filename in filenames:
if not filename.endswith('.h5'):
continue
filepath = os.path.join(dirpath, filename)
node_match = FILE_PATTERN.search(filename)
if not node_match:
continue
node_code = int(node_match.group(1))
channel_match = CHANNEL_PATTERN.search(filename)
channel = int(channel_match.group(1)) if channel_match else -1
dataset = 'aux' if 'aux' in filename else 'data'
stat = os.stat(filepath)
mtime = int(stat.st_mtime)
size = stat.st_size
found_nodes.setdefault(node_code, {'data': False, 'aux': False, 'count': 0, 'last': 0})
found_nodes[node_code]['count'] += 1
found_nodes[node_code]['last'] = max(found_nodes[node_code]['last'], mtime)
if dataset == 'data':
found_nodes[node_code]['data'] = True
else:
found_nodes[node_code]['aux'] = True
cur.execute(
'INSERT INTO files (path, node_code, channel, dataset, size, mtime) VALUES (?, ?, ?, ?, ?, ?)',
(filepath, node_code, channel, dataset, size, mtime)
)
files_counter += 1
print(f"✓ Indexed {files_counter} H5 files")
print(f"✓ Found {len(found_nodes)} positions with data")
# 3. Créer les entrées pour TOUTES les positions (attendues + trouvées)
all_nodes = expected_nodes | set(found_nodes.keys())
for node_code in all_nodes:
is_expected = node_code in expected_nodes
if node_code in found_nodes:
stats = found_nodes[node_code]
has_data = 1 if stats['data'] else 0
has_aux = 1 if stats['aux'] else 0
last_seen = datetime.fromtimestamp(stats['last']).isoformat()
sample_count = stats['count']
else:
# Position attendue mais sans données
has_data = 0
has_aux = 0
last_seen = None
sample_count = 0
cur.execute(
'INSERT INTO positions (node_code, has_data, has_aux, sample_count, last_seen, expected) VALUES (?, ?, ?, ?, ?, ?)',
(node_code, has_data, has_aux, sample_count, last_seen, 1 if is_expected else 0)
)
conn.commit()
# Stats finales
cur.execute('SELECT COUNT(*) FROM positions')
total = cur.fetchone()[0]
cur.execute('SELECT COUNT(*) FROM positions WHERE has_data = 1')
with_data = cur.fetchone()[0]
cur.execute('SELECT COUNT(*) FROM positions WHERE expected = 1')
expected_count = cur.fetchone()[0]
cur.execute('SELECT COUNT(*) FROM positions WHERE expected = 1 AND has_data = 0')
missing = cur.fetchone()[0]
print(f"\n📊 Database Summary:")
print(f" • Total positions in DB: {total}")
print(f" • Expected (from CSV): {expected_count}")
print(f" • With H5 data: {with_data}")
print(f" • Missing (expected but no data): {missing}")
print(f" • Coverage: {(with_data/expected_count*100 if expected_count else 0):.1f}%")
conn.close()
if __name__ == '__main__':
rebuild_db()

114
scripts/rebuild_index.py Executable file
View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python3
"""
Reconstruit l'index.json de la webapp à partir de l'inventaire complet.
Prend en compte tous les fichiers HDF5 sur tous les disques.
"""
import json
from pathlib import Path
from datetime import datetime
from collections import defaultdict
def main():
# Charger l'inventaire
inv_path = Path(r'F:\seismic_webapp\inventory.json')
inv = json.load(open(inv_path))
print(f"Inventaire charge: {len(inv)} fichiers")
# Charger l'index existant pour garder les positions
idx_path = Path(r'F:\seismic_webapp\data\index.json')
old_idx = json.load(open(idx_path))
print(f"Index existant: {len(old_idx.get('nodes', {}))} nodes")
# Construire le nouvel index
nodes = {}
# Copier les positions existantes
for node_id, node_data in old_idx.get('nodes', {}).items():
nodes[node_id] = {
'position': node_data.get('position'),
'dates': {},
'hasDates': False
}
# Ajouter les fichiers de l'inventaire
files_added = 0
for f in inv:
bumper_id = f['bumper_id']
if not bumper_id:
continue
# Créer le node s'il n'existe pas
if bumper_id not in nodes:
nodes[bumper_id] = {
'position': None,
'dates': {},
'hasDates': False
}
# Calculer la date depuis l'epoch
if f['epoch_time']:
dt = datetime.fromtimestamp(f['epoch_time'])
date_str = dt.strftime('%Y-%m-%d')
else:
continue
# Ajouter à la liste des dates
if date_str not in nodes[bumper_id]['dates']:
nodes[bumper_id]['dates'][date_str] = []
# Déterminer les canaux (extraire du nom de fichier)
channel = f['channel']
channels = [channel] if channel else []
# Ajouter le fichier
file_info = {
'path': f['filepath'],
'timestamp': f['epoch_time'],
'channels': channels,
'size_bytes': 0 # On n'a pas cette info
}
# Éviter les doublons
existing_paths = [fi['path'] for fi in nodes[bumper_id]['dates'][date_str]]
if f['filepath'] not in existing_paths:
nodes[bumper_id]['dates'][date_str].append(file_info)
files_added += 1
# Marquer les nodes qui ont des dates
for node_id, node_data in nodes.items():
node_data['hasDates'] = len(node_data['dates']) > 0
# Statistiques
nodes_with_data = sum(1 for n in nodes.values() if n['hasDates'])
total_files = sum(
len(files)
for n in nodes.values()
for files in n['dates'].values()
)
print(f"\nNouvel index:")
print(f" Nodes total: {len(nodes)}")
print(f" Nodes avec donnees: {nodes_with_data}")
print(f" Fichiers indexes: {total_files}")
# Sauvegarder
new_idx = {
'nodes': nodes,
'sampleRateHz': old_idx.get('sampleRateHz', 200),
'generated': datetime.now().isoformat()
}
# Backup de l'ancien
backup_path = idx_path.with_suffix('.json.bak')
with open(backup_path, 'w') as f:
json.dump(old_idx, f)
print(f"\nBackup sauvegarde: {backup_path}")
# Sauvegarder le nouveau
with open(idx_path, 'w') as f:
json.dump(new_idx, f, indent=2)
print(f"Nouvel index sauvegarde: {idx_path}")
if __name__ == '__main__':
main()

39
scripts/show_stats.py Executable file
View File

@@ -0,0 +1,39 @@
import json
from collections import defaultdict
d = json.load(open(r'F:\seismic_webapp\inventory.json'))
by_channel = defaultdict(lambda: {'data': 0, 'aux': 0, 'bumpers': set()})
for f in d:
ch = f['channel'] or 'unknown'
if f['file_type'] == 'data':
by_channel[ch]['data'] += 1
else:
by_channel[ch]['aux'] += 1
if f['bumper_id']:
by_channel[ch]['bumpers'].add(f['bumper_id'])
print('=== RESUME PAR CANAL ===')
print('Canal DATA AUX Bumpers')
print('-' * 35)
for ch in ['ch0', 'ch1', 'ch2', 'ch3', 'ch5', 'ch6', 'ch7', 'ch15', 'unknown']:
if ch in by_channel:
s = by_channel[ch]
total = s['data'] + s['aux']
print(f'{ch:8} {s["data"]:4} {s["aux"]:4} {len(s["bumpers"]):3}')
# Stats globales
total_data = sum(s['data'] for s in by_channel.values())
total_aux = sum(s['aux'] for s in by_channel.values())
all_bumpers = set()
for s in by_channel.values():
all_bumpers.update(s['bumpers'])
print('-' * 35)
print(f'TOTAL {total_data:4} {total_aux:4} {len(all_bumpers):3}')
errors = [f for f in d if f['error']]
print(f'\nErreurs de lecture: {len(errors)} fichiers')
if errors:
for e in errors[:5]:
print(f' - {e["filename"][:50]}...')

31
scripts/test_hdf5.py Executable file
View File

@@ -0,0 +1,31 @@
import h5py
import numpy as np
# Test file
filepath = r'F:\2020-09-22\data\auto_266_143513_b29_13_213605_data_rsn2648_seq1_ch0_1599039547.h5'
with h5py.File(filepath, 'r') as f:
print("=== Structure du fichier ===")
print("Datasets:", list(f.keys()))
if 'adc_values' in f:
d = f['adc_values']
print("\n=== Dataset adc_values ===")
print("Shape:", d.shape)
print("Dtype:", d.dtype)
print("\n=== Attributs du dataset ===")
for k, v in d.attrs.items():
print(f" {k}: {v}")
# Charger un échantillon
sample = d[:2000]
print("\n=== Statistiques (premiers 2000 samples) ===")
print("Min:", np.min(sample))
print("Max:", np.max(sample))
print("Mean:", np.mean(sample))
print("Std:", np.std(sample))
print("RMS:", np.sqrt(np.mean(sample**2)))
print("\n=== Premiers 20 valeurs ===")
print(sample[:20])

201
simple_api.py Normal file
View File

@@ -0,0 +1,201 @@
#!/usr/bin/env python3
from flask import Flask, jsonify, request
from flask_cors import CORS
import json
import h5py
import numpy as np
from pathlib import Path
from datetime import datetime
app = Flask(__name__)
CORS(app)
# Load index once at startup
with open('/data/index.json', 'r') as f:
INDEX = json.load(f)
H5_DIR = Path('/data/h5')
@app.route('/api/nodes', methods=['GET'])
def get_nodes():
nodes_list = []
for node_id, node_info in INDEX['nodes'].items():
file_count = len(node_info.get('files', []))
nodes_list.append({
'id': node_id,
'position': node_info.get('position', {}),
'file_count': file_count,
'hasDates': file_count > 0
})
return jsonify({
'nodes': nodes_list,
'sampleRateHz': 500
})
@app.route('/api/dates', methods=['GET'])
def get_dates():
return jsonify({'dates': INDEX['dates']})
@app.route('/api/migration-status', methods=['GET'])
def get_migration_status():
"""Status de la conversion RAW -> H5"""
total_files = 345 # Connu du projet
h5_files = list(H5_DIR.glob('*.h5'))
converted = len(h5_files)
return jsonify({
'summary': {
'total_files': total_files,
'converted_files': converted,
'percentage': round(converted / total_files * 100, 1),
'status': 'in_progress' if converted < total_files else 'complete'
},
'h5_files_available': converted
})
@app.route('/api/rms-timeline', methods=['GET'])
def get_rms_timeline():
"""Timeline RMS pour un channel et une date donnés"""
date = request.args.get('date')
channel = request.args.get('channel', 'ch0')
if not date:
return jsonify({'error': 'date parameter required'}), 400
# Chercher les fichiers H5 pour cette date
timeline = []
for node_id, node_info in INDEX['nodes'].items():
for file_info in node_info.get('files', []):
if file_info.get('date') == date:
# Pour l'instant, retourner des données mock
# TODO: Calculer le vrai RMS depuis les fichiers H5
timeline.append({
'node_id': node_id,
'timestamp': 0,
'rms': 0 # À calculer depuis H5
})
return jsonify({
'date': date,
'channel': channel,
'timeline': timeline
})
@app.route('/api/data', methods=['GET'])
def get_waveform_data():
"""Données waveform pour un node, date, channel, timestamp"""
node_id = request.args.get('node')
date = request.args.get('date')
channel = request.args.get('channel', 'ch0')
start = float(request.args.get('start', 0))
duration = float(request.args.get('duration', 10))
if not node_id or not date:
return jsonify({'error': 'node and date required'}), 400
# Chercher le fichier H5 correspondant
node_info = INDEX['nodes'].get(node_id)
if not node_info:
return jsonify({'error': 'node not found'}), 404
h5_file = None
for file_info in node_info.get('files', []):
if file_info.get('date') == date:
h5_file = H5_DIR / file_info.get('filename', '')
break
if not h5_file or not h5_file.exists():
return jsonify({'error': 'H5 file not found'}), 404
try:
with h5py.File(h5_file, 'r') as f:
sample_rate = f['metadata'].attrs['sample_rate_hz']
channel_num = int(channel.replace('ch', '').replace('CH', ''))
# Lire les données calibrées
dataset = f[f'calibrated_data/channel_{channel_num + 1}']
start_sample = int(start * sample_rate)
end_sample = int((start + duration) * sample_rate)
# Limiter à la taille du dataset
end_sample = min(end_sample, dataset.shape[0])
if start_sample >= dataset.shape[0]:
return jsonify({'error': 'start time out of range'}), 400
data = dataset[start_sample:end_sample]
# Calculer stats
rms = float(np.sqrt(np.mean(data ** 2)))
peak = float(np.max(np.abs(data)))
return jsonify({
'node_id': node_id,
'date': date,
'channel': channel,
'start': start,
'duration': duration,
'sample_rate': int(sample_rate),
'data': data.tolist(),
'stats': {
'rms': rms,
'peak': peak,
'samples': len(data)
}
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/h5/files', methods=['GET'])
def get_h5_files():
"""Liste des fichiers H5 disponibles avec métadonnées"""
files = []
for h5_path in H5_DIR.glob('*.h5'):
try:
with h5py.File(h5_path, 'r') as f:
files.append({
'filename': h5_path.name,
'size_mb': round(h5_path.stat().st_size / 1024 / 1024, 2),
'duration_sec': int(f['metadata'].attrs.get('duration_sec', 0)),
'sample_rate': int(f['metadata'].attrs.get('sample_rate_hz', 500)),
'channels': int(f['metadata'].attrs.get('n_channels', 4))
})
except:
pass
return jsonify({'files': files, 'count': len(files)})
@app.route('/api/h5/coverage', methods=['GET'])
def get_h5_coverage():
"""Matrice de couverture nodes x dates"""
coverage = {}
for node_id, node_info in INDEX['nodes'].items():
node_dates = [f['date'] for f in node_info.get('files', []) if 'date' in f]
coverage[node_id] = node_dates
return jsonify({
'coverage': coverage,
'total_nodes': len(coverage),
'total_dates': len(INDEX['dates'])
})
@app.route('/api/chat', methods=['POST'])
def chat():
"""Endpoint chat assistant (mock)"""
data = request.json
message = data.get('message', '')
return jsonify({
'response': f"[Mock] Vous avez dit: {message}",
'timestamp': datetime.now().isoformat()
})
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'nodes': len(INDEX['nodes']), 'dates': len(INDEX['dates'])})
if __name__ == '__main__':
print(f"Loaded {len(INDEX['nodes'])} nodes, {len(INDEX['dates'])} dates")
print(f"H5 directory: {H5_DIR}")
app.run(host='0.0.0.0', port=3004)

206
static/SETE_DARF_V4.csv Normal file
View File

@@ -0,0 +1,206 @@
1,2,,Sete-Pilot-20200903.csv,,6,7,8,9,10,11,12,,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,,,,,,,,,47,,,All commands failed,-10,,,,,,,,,,,,,,,,,,97,196,1
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Comment_ori,,,,,,,,,,,,,,,,,,,,,
,FAUX,,,,,,,,Deployment,,,,,Check FIX,Recovery,,,,,Check FIX,,,,,,,,,,,,,,,,,,,,Last message from position log,,,,,,,,,,,145,,,,,,,,Fished,-10,,,,,,,,,,,,,,,,,,,,
concat,Line,Point,NodeCode,Index,Preplot Easting,Preplot Northing,Preplot Depth,Aslaid Time,Aslaid Easting,Aslaid Northing,Aslaid Depth,,Aslaid Tide Offset,Aslaid Azimuth,Recovered Time,Recovered Easting,Recovered Northing,Recovered Depth,Recovered Tide Offset,Recovered Azimuth,PreplotToAslaidDistance,PreplotToAslaidBearing,AslaidToRecoveredDistance,AslaidToRecoveredBearing,RecoveredToPreplotDistance,RecoveredToPreplotBearing,PreplotToAslaidAlongTrack,PreplotToAslaidCrossTrack,RecoveredToPreplotAlongTrack,RecoveredToPreplotCrossTrack,AslaidToRecoveredAlongTrack,AslaidToRecoveredCrossTrack,DeployedComments,RecoveredComments,Flag,Date,JD,Time,JDTIME,time,X_Pos,Y_Pos,Status,File,X_Comp,Y_Comp,Comment,comment_bis,duration,Comment_ori,log_download,date,comment_pickup,date sent onshore,,Comment,,,Fishermen,-20,,,,,,,,,,,,,,,,,,,,
10001,1000,5000,4,1,559867.24,4797453,0.00,2020-Sep-03 18:41:52.000,0,0,0,1000/5000,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:10:09""",559951.24,4797507.26,PMU-A,C:\SpiceRack\20200904\positions_log.csv,559869.849,4797451.921,In Gabia_1,,,In Gabia_1,OK,20200912,,,,20,,,Gabia missed,10,,,,,,,,,,,,,,,,,,,,
10002,1000,5004,10,1,559949.16,4797510.36,0.00,2020-Sep-03 18:41:52.000,0,0,0,1000/5004,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:10:51""",560035.89,4797566.14,LANDING,C:\SpiceRack\20200904\positions_log.csv,559950.057,4797509.16,In Gabia_1,,,In Gabia_1,OK,20200912,,,,20,,,In Gabia_1,20,,,,,,,,,,,,,,,,,,,,
10003,1000,5008,11,1,560031.07,4797567.72,0.00,2020-Sep-03 18:41:52.000,0,0,0,1000/5008,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:15:39""",560117.19,4797620.55,NULL,C:\SpiceRack\20200904\positions_log.csv,560035.816,4797565.643,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,In Gabia_2,20,,,,,,,,,,,,,,,,,,,,
10004,1000,5012,15,1,560112.99,4797625.07,0.00,2020-Sep-03 18:41:52.000,560116.82,4797621.72,37,1000/5012,0.0,147.40,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,,,,,,560115.915,4797622.84,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,In Gabia_3,20,,,,,,,,,,,,,,,,,,,,
10006,1000,5020,37,1,560276.82,4797739.79,0.00,2020-Sep-03 18:41:52.000,560284.01,4797736.15,35,1000/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:39:33""",560367.98,4797796.99,LANDED,C:\SpiceRack\20200904\positions_log.csv,560281.716,4797739.475,In Gabia_2,,,In Gabia_2,OK,20200912,Eastern Egg HS,13-Sep,,20,,,In Gabia_4,20,,,,,,,,,,,,,,,,,,,,
10007,1000,5024,40,1,560358.73,4797797.15,0.00,2020-Sep-03 18:41:52.000,560367.98,4797796.99,36,1000/5024,0.0,141.70,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:48:23""",560441.57,4797848.22,NULL,C:\SpiceRack\20200904\positions_log.csv,560367.603,4797799.043,Surface,,,Surface,OK,20200912,,14-Sep,,10,,,in Gabia_5,20,,,,,,,,,,,,,,,,,,,,
10008,1000,5028,43,1,560440.65,4797854.5,0.00,2020-Sep-03 18:41:52.000,560439.27,4797850.52,-10,1000/5028,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:45:47""",560527.58,4797908.74,LANDED,C:\SpiceRack\20200904\positions_log.csv,560452.745,4797859.883,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,In Gabia_6,20,,,,,,,,,,,,,,,,,,,,
10009,1000,5032,44,1,560522.56,4797911.86,0.00,2020-Sep-03 18:41:52.000,560527.58,4797908.74,37,1000/5032,0.0,144.60,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:45:44""",560599.66,4797961.54,LANDED,C:\SpiceRack\20200904\positions_log.csv,560527.112,4797910.961,In Gabia_1,,,In Gabia_1,OK,20200912,,,,20,,,Manta_10484,-30,,,,,,,,,,,,,,,,,,,,
10010,1000,5036,45,1,560604.48,4797969.22,0.00,2020-Sep-03 18:41:52.000,560599.66,4797961.54,37,1000/5036,0.0,133.20,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:25:50""",560689.12,4798023.32,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560597.021,4797963.004,In Gabia_1,,,In Gabia_1,OK,20200912,Front truster HS,13-Sep,,20,,,Manta_12025,-30,,,,,,,,,,,,,,,,,,,,
10011,1000,5040,52,1,560686.39,4798026.58,0.00,2020-Sep-03 18:41:52.000,560687.96,4798024.23,35,1000/5040,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:27:20""",560773.34,4798084.4,LANDING,C:\SpiceRack\20200904\positions_log.csv,560687.314,4798026.735,Fishermen,,,Fishermen,OK,20200912,Fishing boat,13-Sep,,-20,,,Manta_1240,-30,,,,,,,,,,,,,,,,,,,,
10012,1000,5044,54,1,560768.31,4798083.93,0.00,2020-Sep-03 18:41:52.000,560773.34,4798084.4,37,1000/5044,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:28:52""",560853.11,4798142.13,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560771.925,4798086.655,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,Manta_12443,-30,,,,,,,,,,,,,,,,,,,,
10013,1000,5048,56,1,560850.22,4798141.29,0.00,2020-Sep-03 18:41:52.000,0,0,0,1000/5048,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:30:32""",560936.14,4798197.59,LANDING,C:\SpiceRack\20200904\positions_log.csv,560852.9,4798141.947,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,Manta_12614,-30,,,,,,,,,,,,,,,,,,,,
10014,1000,5052,63,1,560932.14,4798198.65,0.00,2020-Sep-03 18:41:52.000,560936.14,4798197.59,37,1000/5052,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:33:50""",561013.53,4798249.08,LANDED,C:\SpiceRack\20200904\positions_log.csv,560934.867,4798201.855,Fishermen,,,Fishermen,OK,20200913,Fishing boat,13-Sep,,-20,,,Manta_2753,-30,,,,,,,,,,,,,,,,,,,,
10015,1000,5056,67,1,561014.05,4798256.01,0.00,2020-Sep-03 18:41:52.000,561013.53,4798249.08,37,1000/5056,0.0,138.90,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:39:55""",561099.49,4798310.67,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561012.719,4798250.397,In Gabia_1,,,In Gabia_1,OK,20200914,,,,20,,,Manta_4071,-30,,,,,,,,,,,,,,,,,,,,
10016,1000,5060,72,1,561095.97,4798313.36,0.00,2020-Sep-03 18:41:52.000,561097.53,4798313.11,35,1000/5060,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:41:21""",561176.33,4798369.57,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561096.612,4798314.914,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,Navigation issue,-10,,,,,,,,,,,,,,,,,,,,
10017,1000,5064,73,1,561177.88,4798370.72,0.00,2020-Sep-03 18:41:52.000,561181.16,4798371.55,34,1000/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:45:18""",561177.18,4798341.87,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561178.199,4798370.267,In Gabia_1,,,In Gabia_1,OK,20200912,,14-Sep,,20,,,No com,-10,,,,,,,,,,,,,,,,,,,,
10018,1000,5068,78,1,561259.8,4798428.08,0.00,2020-Sep-03 18:41:52.000,0,0,0,1000/5068,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:54:44""",561342.98,4798481.82,NULL,C:\SpiceRack\20200904\positions_log.csv,561180.452,4798342.119,ROV,ROV_20200914_01,0:29,Gabia missed,Fail,,,,,10,,,No take off,-20,,,,,,,,,,,,,,,,,,,,
10019,1000,5072,84,1,561341.71,4798485.44,0.00,2020-Sep-03 18:41:52.000,561343.68,4798482.74,-10,1000/5072,0.0,141.70,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 10:56:07""",561423.11,4798539.88,NULL,C:\SpiceRack\20200904\positions_log.csv,561344.171,4798485.89,Fishermen,,,Fishermen,Fail,,"No Com, Electronic HS",14-Sep,,-20,,,No wake Up,-20,,,,,,,,,,,,,,,,,,,,
10020,1000,5076,87,1,561423.63,4798542.8,0.00,2020-Sep-03 18:41:52.000,561423,4798540.05,37,1000/5076,0.0,127.60,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-04 12:50:32""",561613.17,4796861.24,NULL,C:\SpiceRack\20200904\positions_log.csv,561408.546,4798520.607,In Gabia_1,,,In Gabia_1,OK,20200913,Eastern Egg and front truster HS,13-Sep,,20,,,Surface,10,,,,,,,,,,,,,,,,,,,,
10021,1000,5080,88,1,561505.54,4798600.15,0.00,2020-Sep-03 18:41:52.000,561508.79,4798599.75,37,1000/5080,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-04 12:50:32""",561636.82,4796505.46,NULL,C:\SpiceRack\20200904\positions_log.csv,561508.137,4798603.16,In Gabia_1,,,In Gabia_1,OK,20200912,Eastern Egg and front truster HS,13-Sep,,20,,,Surface failed,-10,,,,,,,,,,,,,,,,,,,,
10022,1000,5084,91,1,561587.46,4798657.51,0.00,2020-Sep-03 18:41:52.000,561590.22,4798654.3,37,1000/5084,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 11:06:54""",561668.07,4798713.18,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561589.107,4798657.864,ROV,ROV_20200914_02,0:15,No wake Up,OK,,,15-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
10023,1000,5088,99,1,561669.37,4798714.87,0.00,2020-Sep-03 18:41:52.000,561674.16,4798725.83,35,1000/5088,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 11:08:59""",561755.37,4798770.24,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561671.21,4798716.041,In Gabia_1,,,In Gabia_1,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
10024,1000,5092,100,1,561751.29,4798772.23,0.00,2020-Sep-03 18:41:53.000,561755.58,4798770.13,37,1000/5092,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:10:57""",561837.23,4798830.21,LANDED,C:\SpiceRack\20200904\positions_log.csv,561754.318,4798774.606,In Gabia_1,,,In Gabia_1,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
10025,1000,5096,102,1,561833.2,4798829.58,0.00,2020-Sep-03 18:41:53.000,561837.23,4798830.21,37,1000/5096,0.0,147.40,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,561837.437,4798834.59,In Gabia_1,,,In Gabia_1,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
10005,1000,5016,156,1,560194.9,4797682.43,0.00,2020-Sep-04 12:16:00.000,560196.9,4796699.2,0,1000/5016,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:52,247184152,"""2020-09-03 09:36:53""",560283.82,4797736.63,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560198.796,4797676.377,Manta_10484,Manta_10484,,Manta_10484,,,,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
20002,1012,5004,2,1,560121.23,4797264.61,0.00,2020-Sep-03 18:41:53.000,560115.92,4797263.54,37,1012/5004,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:46:45""",560198.22,4797324.27,LANDING,C:\SpiceRack\20200904\positions_log.csv,560117.038,4797260.288,In Gabia_2,,,In Gabia_2,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20001,1012,5000,28,1,560039.31,4797207.25,0.00,2020-Sep-03 18:41:53.000,0,0,0,1012/5000,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:48:24""",560115.92,4797263.54,LANDING,C:\SpiceRack\20200904\positions_log.csv,560085.541,4797284.652,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
20011,1012,5040,90,1,560858.46,4797780.83,0.00,2020-Sep-04 11:56:00.000,560854.7,4797782.2,0,1012/5040,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:02:22""",560848.72,4797786.56,NAVIG,C:\SpiceRack\20200904\positions_log.csv,560856.964,4797781.978,ROV,ROV_20200914_07,0:28,Manta_2753,Fail,,"Right rear truster twisted, left rear truster locked",15-Sep,,-30,,,,,,,,,,,,,,,,,,,,,,,,
20025,1012,5096,93,1,562005.28,4798583.84,0.00,2020-Sep-03 18:41:54.000,562004.69,4798584.18,33,1012/5096,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:30:48""",560215.02,4796956.67,LANDED,C:\SpiceRack\20200904\positions_log.csv,562005.278,4798583.838,In Gabia_2,,,In Gabia_2,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20024,1012,5092,94,1,561923.36,4798526.48,0.00,2020-Sep-03 18:41:54.000,561922.82,4798527.69,38,1012/5092,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 11:21:12""",562000.87,4798585.11,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561924.089,4798526.228,In Gabia_2,,,In Gabia_2,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20003,1012,5008,103,1,560203.14,4797321.97,0.00,2020-Sep-03 18:41:53.000,560198.22,4797324.27,37,1012/5008,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:44:59""",560278.32,4797379.55,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560200.097,4797319.715,Surface,,,Surface,OK,20200912,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
20004,1012,5012,104,1,560285.06,4797379.33,0.00,2020-Sep-03 18:41:53.000,560278.76,4797378.81,37,1012/5012,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:43:39""",560361.72,4797437.23,LANDING,C:\SpiceRack\20200904\positions_log.csv,560280.22,4797375.689,ROV,ROV_20200914_05,0:45,All commands failed,,,Right Rear truster HS,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
20023,1012,5088,109,1,561841.45,4798469.12,0.00,2020-Sep-03 18:41:53.000,561837.73,4798467.75,35,1012/5088,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:23:33""",561921.31,4798530.37,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561838.94,4798465.796,In Gabia_2,,,In Gabia_2,OK,20200914,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20022,1012,5084,115,1,561759.53,4798411.76,0.00,2020-Sep-03 18:41:53.000,561769.11,4798417.87,35,1012/5084,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:27:00""",561177.03,4798346.62,INIT,C:\SpiceRack\20200904\positions_log.csv,561768.498,4798416.55,Gabia missed,,,Gabia missed,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
20005,1012,5016,124,1,560366.97,4797436.68,0.00,2020-Sep-03 18:41:53.000,560361.72,4797437.23,37,1012/5016,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:23:00""",560443.65,4797502.41,LANDING,C:\SpiceRack\20200904\positions_log.csv,560364.649,4797432.797,ROV,ROV_20200914_06,0:15,No wake Up,OK,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
20007,1012,5024,128,1,560530.8,4797551.4,0.00,2020-Sep-03 18:41:53.000,560529.16,4797555.9,36,1012/5024,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:19:39""",560606.9,4797608.23,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560528.912,4797553.528,In Gabia_2,,,In Gabia_2,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20021,1012,5080,131,1,561677.62,4798354.41,0.00,2020-Sep-03 18:41:53.000,0,0,0,1012/5080,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:24:49""",561765.91,4798418.77,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561772.233,4798451.29,ROV,ROV_20200914_03,0:25,Navigation issue,OK,,,15-Sep,,-10,,,,,,,,,,,,,,,,,,,,,,,,
20020,1012,5076,134,1,561595.7,4798297.05,0.00,2020-Sep-03 18:41:53.000,561590.55,4798294.8,36,1012/5076,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:26:54""",561774.36,4798449.79,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561590.772,4798294.037,In Gabia_2,,,In Gabia_2,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20019,1012,5072,136,1,561513.79,4798239.69,0.00,2020-Sep-03 18:41:53.000,561506.4,4798241.76,38,1012/5072,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:40:24""",561587.63,4798297.88,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561506.805,4798240.623,ROV,ROV_20200914_04,0:08,Navigation issue,OK,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
20018,1012,5068,143,1,561431.87,4798182.33,0.00,2020-Sep-03 18:41:53.000,561426.34,4798183.54,38,1012/5068,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:42:13""",561506.4,4798241.76,LANDING,C:\SpiceRack\20200904\positions_log.csv,561429.539,4798180.879,In Gabia_2,,,In Gabia_2,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20017,1012,5064,147,1,561349.96,4798124.98,0.00,2020-Sep-03 18:41:53.000,561344.38,4798127.78,37,1012/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:48:11""",561182.12,4798346.76,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561346.795,4798122.986,Surface,,,Surface,OK,20200912,Eastern Egg and front truster HS,13-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
20006,1012,5020,172,1,560448.89,4797494.04,0.00,2020-Sep-03 18:41:53.000,560443.65,4797502.41,37,1012/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:21:02""",560527.65,4797555.29,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560445.974,4797492.292,ROV,ROV_20200915_01,00:11,All commands failed,OK,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
20016,1012,5060,175,1,561268.04,4798067.62,0.00,2020-Sep-03 18:41:53.000,561264.85,4798069.07,37,1012/5060,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:45:44""",561343.88,4798128.72,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561265.234,4798067.596,In Gabia_2,,,In Gabia_2,OK,20200912,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20008,1012,5028,177,1,560612.72,4797608.76,0.00,2020-Sep-03 18:41:53.000,560607.4,4797607.81,35,1012/5028,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:17:37""",560686.24,4797660.78,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560607.02,4797608.067,In Gabia_2,,,In Gabia_2,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20015,1012,5056,184,1,561186.13,4798010.26,0.00,2020-Sep-03 18:41:53.000,561179.92,4798010.28,38,1012/5056,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:48:11""",561264.81,4798069.27,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561181.484,4798006.14,In Gabia_2,,,In Gabia_2,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
20014,1012,5052,188,1,561104.21,4797952.9,0.00,2020-Sep-03 18:41:53.000,561099.09,4797952.13,37,1012/5052,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:56:46""",561179.92,4798010.42,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561100.592,4797948.599,Fishermen,,,Fishermen,OK,20200914,,14-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
20013,1012,5048,192,1,561022.3,4797895.55,0.00,2020-Sep-03 18:41:53.000,561017.51,4797898.16,38,1012/5048,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 11:58:48""",561097.92,4797954.02,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561019.934,4797894.387,In Gabia_2,,,In Gabia_2,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20012,1012,5044,198,1,560940.38,4797838.19,0.00,2020-Sep-03 18:41:53.000,0,0,0,1012/5044,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:00:21""",561017.51,4797898.16,LANDING,C:\SpiceRack\20200904\positions_log.csv,560830.183,4797774.82,In Gabia_2,,,In Gabia_2,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
20010,1012,5036,207,1,560776.55,4797723.47,0.00,2020-Sep-03 18:41:53.000,560771.05,4797725.37,38,1012/5036,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,,,,,,560773.729,4797720.893,Surface,,,Surface,OK,20200912,,15-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
20009,1012,5032,209,1,560694.63,4797666.12,0.00,2020-Sep-03 18:41:53.000,560686.06,4797660.8,38,1012/5032,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:53,247184153,"""2020-09-03 12:15:39""",560771.05,4797725.37,LANDING,C:\SpiceRack\20200904\positions_log.csv,560688.193,4797656.956,In Gabia_2,,,In Gabia_2,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30001,1024,5000,7,1,560211.39,4796961.51,0.00,2020-Sep-03 18:41:54.000,560215.02,4796956.67,38,1024/5000,0.0,136.10,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:32:17""",560298.03,4797014.52,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560213.45,4796961.015,In Gabia_3,,,In Gabia_3,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30016,1024,5060,12,1,561440.11,4797821.87,0.00,2020-Sep-03 18:41:54.000,561445.96,4797815.07,39,1024/5060,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-04 14:32:56""",561579.41,4797901.1,SLEEP,C:\SpiceRack\20200904\positions_log.csv,561441.385,4797821.035,Surface,,,Surface,OK,20200912,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
30017,1024,5064,13,1,561522.03,4797879.23,0.00,2020-Sep-03 18:41:54.000,561581.63,4797895.28,38,1024/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-04 14:32:56""",561592.27,4797937.73,NULL,C:\SpiceRack\20200904\positions_log.csv,561581.19,4797895.56,In Gabia_3,,,In Gabia_3,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30020,1024,5076,19,1,561767.77,4798051.3,0.00,2020-Sep-04 11:19:00.000,561769,4798051.8,0,1024/5076,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:20:27""",561875.34,4798085.45,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561765.567,4798052.117,ROV,ROV_20200915_05,00:21,Manta_12025,,,,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
30015,1024,5056,21,1,561358.2,4797764.52,0.00,2020-Sep-03 18:41:54.000,561418.47,4797772.45,39,1024/5056,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:08:33""",561445.96,4797815.07,LANDING,C:\SpiceRack\20200904\positions_log.csv,561418.361,4797772.865,In Gabia_3,,,In Gabia_3,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30002,1024,5004,34,1,560293.3,4797018.87,0.00,2020-Sep-03 18:41:54.000,560298.26,4797014.79,38,1024/5004,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:34:00""",560377.78,4797071.06,LANDING,C:\SpiceRack\20200904\positions_log.csv,560294.922,4797020.2,In Gabia_3,,,In Gabia_3,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30003,1024,5008,46,1,560375.22,4797076.22,0.00,2020-Sep-03 18:41:54.000,560377.78,4797071.06,38,1024/5008,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:35:42""",560459.58,4797131.65,LANDING,C:\SpiceRack\20200904\positions_log.csv,560377.035,4797077.075,In Gabia_3,,,In Gabia_3,OK,20200914,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30018,1024,5068,55,1,561603.94,4797936.59,0.00,2020-Sep-03 18:41:54.000,561616,4797933.93,35,1024/5068,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:12:46""",561686.5,4797991.88,LANDING,C:\SpiceRack\20200904\positions_log.csv,561615.628,4797934.243,Surface,,,Surface,Fail,20200912,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
30019,1024,5072,83,1,561685.86,4797993.95,0.00,2020-Sep-03 18:41:54.000,561686.5,4797991.88,39,1024/5072,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,,,,,,561686.84,4797993.526,ROV,ROV_20200915_04,,No take off,,,Easter Egg flashing but not released,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
30004,1024,5012,86,1,560457.13,4797133.58,0.00,2020-Sep-03 18:41:54.000,560459.58,4797131.65,38,1024/5012,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,560458.967,4797136.786,In Gabia_3,,,In Gabia_3,OK,,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30005,1024,5016,89,1,560539.05,4797190.94,0.00,2020-Sep-03 18:41:54.000,0,0,0,1024/5016,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:43:23""",560620.54,4797246.59,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560479.91,4797095.516,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
30006,1024,5020,129,1,560620.96,4797248.3,0.00,2020-Sep-03 18:41:54.000,560621.9,4797245.96,37,1024/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:43:52""",560619.32,4797170.24,NAVIG,C:\SpiceRack\20200904\positions_log.csv,560622.526,4797249.315,Gabia missed,,,Gabia missed,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
30007,1024,5024,132,1,560702.88,4797305.65,0.00,2020-Sep-03 18:41:54.000,0,0,0,1024/5024,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:46:29""",560800.6,4797380.53,LANDING,C:\SpiceRack\20200904\positions_log.csv,560689.369,4797241.869,ROV,ROV_20200915_02,00:20,Navigation issue,OK,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
30021,1024,5080,139,1,561849.69,4798108.66,0.00,2020-Sep-03 18:41:54.000,561875.68,4798085.53,38,1024/5080,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:20:59""",561932.8,4798165.33,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561875.649,4798086.767,In Gabia_3,,,In Gabia_3,OK,20200912,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30008,1024,5028,146,1,560784.79,4797363.01,0.00,2020-Sep-03 18:41:54.000,560800.6,4797380.53,38,1024/5028,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:47:04""",560783.34,4797363.17,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560785.95,4797365.636,In Gabia_3,,,In Gabia_3,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30022,1024,5084,149,1,561931.61,4798166.02,0.00,2020-Sep-03 18:41:54.000,0,0,0,1024/5084,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 18:03:58""",561082.14,4797637.81,INIT,C:\SpiceRack\20200904\positions_log.csv,561932.877,4798166.765,In Gabia_3,,,In Gabia_3,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30009,1024,5032,152,1,560866.71,4797420.37,0.00,2020-Sep-03 18:41:54.000,0,0,0,1024/5032,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:49:12""",560950.07,4797473.85,LANDING,C:\SpiceRack\20200904\positions_log.csv,560799.911,4797386.891,Surface,,,Surface,OK,20200912,,15-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
30010,1024,5036,153,1,560948.62,4797477.73,0.00,2020-Sep-03 18:41:54.000,560950.07,4797473.85,38,1024/5036,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:55:49""",561032.79,4797532.38,LANDING,C:\SpiceRack\20200904\positions_log.csv,560949.103,4797477.235,In Gabia_3,,,In Gabia_3,OK,20200913,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30023,1024,5088,154,1,562013.52,4798223.38,0.00,2020-Sep-03 18:41:54.000,562015.28,4798220.02,38,1024/5088,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:23:55""",562103.55,4798277.46,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562013.738,4798221.875,In Gabia_3,,,In Gabia_3,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
30025,1024,5096,157,1,562177.35,4798338.09,0.00,2020-Sep-03 18:41:54.000,562182.94,4798336.65,39,1024/5096,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 15:28:56""",560382.14,4796717.02,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562184.064,4798339.728,Surface,,,Surface,OK,20200912,Front truster HS,13-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
30014,1024,5052,164,1,561276.28,4797707.16,0.00,2020-Sep-03 18:41:54.000,561280.89,4797703.67,38,1024/5052,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-04 14:31:44""",561409.05,4797770.28,NULL,C:\SpiceRack\20200904\positions_log.csv,561279.669,4797705.655,ROV,ROV_20200915_03,00:14,No take off,OK,,Easter Egg flashing but not released. Handle twisted. Foot twisted.,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
30024,1024,5092,179,1,562095.44,4798280.73,0.00,2020-Sep-03 18:41:54.000,0,0,0,1024/5092,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:25:41""",562183.21,4798336.82,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562099.891,4798280.664,Surface,,,Surface,fail,20200912,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
30011,1024,5040,194,1,561030.54,4797535.09,0.00,2020-Sep-03 18:41:54.000,561032.79,4797532.38,38,1024/5040,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 13:57:44""",561115.93,4797588.62,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561031.33,4797533.846,Fishermen,,,Fishermen,OK,20200912,Fishing boat,13-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
30012,1024,5044,199,1,561112.45,4797592.44,0.00,2020-Sep-03 18:41:54.000,561116.39,4797589.21,38,1024/5044,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 18:15:00""",561081.53,4797635.15,SLEEP,C:\SpiceRack\20200904\positions_log.csv,561114.812,4797592.912,In Gabia_3,,,In Gabia_3,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
30013,1024,5048,204,1,561194.37,4797649.8,0.00,2020-Sep-03 18:41:54.000,561081.53,4797635.15,-10,1024/5048,0.0,255.10,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:54,247184154,"""2020-09-03 14:00:24""",561279.81,4797702.23,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561085.145,4797636.548,ROV,ROV_20200914_08,0:19,No take off,OK,,"Front truster broken, left rear truster broken",,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40025,1036,5096,1,1,562349.42,4798092.35,0.00,2020-Sep-03 18:41:55.000,562345.25,4798095.82,39,1036/5096,0.0,320.30,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:38:41""",560558.73,4796466.74,LANDED,C:\SpiceRack\20200904\positions_log.csv,562348.147,4798090.22,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40020,1036,5076,29,1,561939.85,4797805.56,0.00,2020-Sep-03 18:41:55.000,0,0,0,1036/5076,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:39:39""",562015.28,4797864.19,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561938.12,4797804.082,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40008,1036,5028,58,1,560956.87,4797117.27,0.00,2020-Sep-03 18:41:55.000,560952.47,4797119.6,39,1036/5028,0.0,331.70,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:11:52""",561033.05,4797174.56,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560955.049,4797117.106,In Gabia_4,,,In Gabia_4,Fail,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40002,1036,5004,59,1,560465.37,4796773.12,0.00,2020-Sep-03 18:41:55.000,0,0,0,1036/5004,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:26:16""",560542.13,4796830.1,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560516.925,4796829.213,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
40007,1036,5024,60,1,560874.95,4797059.91,0.00,2020-Sep-03 18:41:55.000,560869.67,4797062.62,39,1036/5024,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:13:02""",560952.47,4797119.6,LANDED,C:\SpiceRack\20200904\positions_log.csv,560870.264,4797060.508,In Gabia_4,,,In Gabia_4,OK,20200912,Eastern Egg and front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40023,1036,5088,61,1,562185.59,4797977.63,0.00,2020-Sep-03 18:41:55.000,562184.43,4797977.33,40,1036/5088,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:33:57""",562264.35,4798037.42,LANDING,C:\SpiceRack\20200904\positions_log.csv,562183.736,4797976.341,Surface,,,Surface,OK,20200912,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
40021,1036,5080,68,1,562021.76,4797862.92,0.00,2020-Sep-03 18:41:55.000,562017.15,4797864.24,38,1036/5080,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:38:13""",562097.3,4797921.75,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562018.724,4797862.732,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40004,1036,5012,79,1,560629.2,4796887.84,0.00,2020-Sep-03 18:41:55.000,560624.31,4796890.41,39,1036/5012,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:23:23""",560705.23,4796944.96,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560626.537,4796887.91,In Gabia_4,,,In Gabia_4,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40003,1036,5008,92,1,560547.29,4796830.48,0.00,2020-Sep-03 18:41:55.000,560541.67,4796829.97,39,1036/5008,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:24:43""",560624.31,4796890.41,LANDING,C:\SpiceRack\20200904\positions_log.csv,560543.26,4796827.954,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40022,1036,5084,95,1,562103.68,4797920.27,0.00,2020-Sep-03 18:41:55.000,562097.86,4797921.15,38,1036/5084,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:35:58""",562184.48,4797977.26,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562100.521,4797918.079,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40006,1036,5020,98,1,560793.03,4797002.55,0.00,2020-Sep-03 18:41:55.000,560788.07,4797001.68,39,1036/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:15:20""",560869.67,4797062.62,LANDING,C:\SpiceRack\20200904\positions_log.csv,560789.596,4797001.951,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40005,1036,5016,107,1,560711.12,4796945.19,0.00,2020-Sep-03 18:41:55.000,560706.95,4796941.36,35,1036/5016,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:16:33""",560788.07,4797001.68,LANDING,C:\SpiceRack\20200904\positions_log.csv,560707.482,4796943.492,Surface,,,Surface,OK,20200913,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
40024,1036,5092,125,1,562267.51,4798034.99,0.00,2020-Sep-03 18:41:55.000,562264.35,4798037.42,39,1036/5092,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:32:01""",562345.25,4798095.82,LANDED,C:\SpiceRack\20200904\positions_log.csv,562268.344,4798035.57,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
40019,1036,5072,159,1,561857.93,4797748.2,0.00,2020-Sep-03 18:41:55.000,561854.63,4797752.19,39,1036/5072,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:45:09""",561937.53,4797807.32,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561856.892,4797750.094,In Gabia_4,,,In Gabia_4,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40018,1036,5068,165,1,561776.02,4797690.84,0.00,2020-Sep-03 18:41:55.000,561772.28,4797692.42,39,1036/5068,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:47:07""",561854.63,4797752.19,LANDING,C:\SpiceRack\20200904\positions_log.csv,561774.503,4797689.653,Fished,EE_20200915_01,,No com,,,Water inside - Not operative,15-Sep,,-10,,,,,,,,,,,,,,,,,,,,,,,,
40017,1036,5064,166,1,561694.1,4797633.49,0.00,2020-Sep-03 18:41:55.000,561688.08,4797630.81,35,1036/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:49:02""",561772.28,4797692.42,LANDING,C:\SpiceRack\20200904\positions_log.csv,561690.857,4797632.607,Fished,EE_20200915_02,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40012,1036,5044,169,1,561284.53,4797346.7,0.00,2020-Sep-03 18:41:55.000,561280.16,4797349.01,39,1036/5044,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:01:50""",561362.94,4797406.42,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561281.64,4797345.104,In Gabia_4,,,In Gabia_4,OK,20200912,Eastern Egg HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40015,1036,5056,170,1,561530.27,4797518.77,0.00,2020-Sep-03 18:41:55.000,561524.03,4797522.83,39,1036/5056,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,,,,,,561527.75,4797521.011,In Gabia_4,,,In Gabia_4,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40011,1036,5040,180,1,561202.61,4797289.34,0.00,2020-Sep-03 18:41:55.000,561197.96,4797282.17,35,1036/5040,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:03:10""",561280.16,4797349.01,LANDING,C:\SpiceRack\20200904\positions_log.csv,561198.278,4797284.676,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40010,1036,5036,181,1,561120.7,4797231.98,0.00,2020-Sep-03 18:41:55.000,561114.59,4797231.45,39,1036/5036,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:04:52""",561197.96,4797282.17,LANDING,C:\SpiceRack\20200904\positions_log.csv,561117.732,4797229.509,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40009,1036,5032,182,1,561038.78,4797174.62,0.00,2020-Sep-03 18:41:55.000,561033.13,4797174.35,39,1036/5032,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:10:09""",561114.59,4797231.45,LANDING,C:\SpiceRack\20200904\positions_log.csv,561034.728,4797173.322,Fished,EE_20200915_03,,No wake Up,,,Water inside - Not operative,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
40014,1036,5052,196,1,561448.36,4797461.41,0.00,2020-Sep-03 18:41:55.000,561446.02,4797465.12,39,1036/5052,0.0,334.50,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:58:41""",561524.03,4797522.83,LANDING,C:\SpiceRack\20200904\positions_log.csv,561447.941,4797461.812,In Gabia_4,,,In Gabia_4,OK,20200913,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40001,1036,5000,197,1,560383.46,4796715.76,0.00,2020-Sep-03 18:41:55.000,560381.92,4796717.07,39,1036/5000,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,560382.385,4796716.119,In Gabia_4,,,In Gabia_4,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
40013,1036,5048,203,1,561366.44,4797404.05,0.00,2020-Sep-03 18:41:55.000,561362.93,4797405.85,39,1036/5048,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:00:08""",561446.02,4797465.12,LANDED,C:\SpiceRack\20200904\positions_log.csv,561365.638,4797405.012,In Gabia_4,,,In Gabia_4,OK,20200913,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
40016,1036,5060,208,1,561612.19,4797576.13,0.00,2020-Sep-04 10:34:00.000,561617.1,4797575.4,0,1036/5060,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 14:50:22""",561689.35,4797633.22,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561616.807,4797573.081,ROV,ROV_20200915_06,00:49,Manta_12443,,,,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
50023,1048,5088,16,1,562357.67,4797731.89,0.00,2020-Sep-03 18:41:56.000,562364.06,4797731.22,40,1048/5088,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:27:58""",562444.78,4797789.45,LANDING,C:\SpiceRack\20200904\positions_log.csv,562365.813,4797735.62,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
50024,1048,5092,18,1,562439.58,4797789.24,0.00,2020-Sep-03 18:41:56.000,562444.78,4797789.45,40,1048/5092,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:29:40""",562525.03,4797844.5,LANDED,C:\SpiceRack\20200904\positions_log.csv,562445.395,4797793.56,Gabia missed,,,Gabia missed,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
50025,1048,5096,24,1,562521.5,4797846.6,0.00,2020-Sep-03 18:41:56.000,562525.03,4797844.5,40,1048/5096,0.0,141.70,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:43:45""",560721.14,4796224.79,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562523.401,4797845.446,In Gabia_4,,,In Gabia_4,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
50022,1048,5084,35,1,562275.75,4797674.53,0.00,2020-Sep-03 18:41:56.000,562279.55,4797672.75,39,1048/5084,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:29:02""",562447.28,4797840.15,NULL,C:\SpiceRack\20200904\positions_log.csv,562279.206,4797674.313,Surface,,,Surface,OK,20200912,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
50021,1048,5080,36,1,562193.84,4797617.17,0.00,2020-Sep-03 18:41:56.000,0,0,0,1048/5080,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:25:09""",562278.63,4797671.28,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562133.088,4797594.781,Fished,EE_20200912_02,,Fished,OK,20200913,Eastern Egg HS,13-Sep,,-10,,,,,,,,,,,,,,,,,,,,,,,,
50005,1048,5016,47,1,560883.19,4796699.45,0.00,2020-Sep-04 09:56:00.000,560911.4,4796699.2,0,1048/5016,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:52:27""",561192.97,4796932.48,NAVIG,C:\SpiceRack\20200904\positions_log.csv,560883.164,4796698.138,ROV,ROV_20200915_07,00:27,Manta_1240,,,,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
50010,1048,5036,51,1,561292.77,4796986.24,0.00,2020-Sep-03 18:41:56.000,0,0,0,1048/5036,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:02:00""",561378.14,4797042.43,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561443.887,4797094.158,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
50004,1048,5012,53,1,560801.28,4796642.09,0.00,2020-Sep-03 18:41:55.000,560805.24,4796641.73,38,1048/5012,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-04 13:57:40""",560880.65,4796701.28,NULL,C:\SpiceRack\20200904\positions_log.csv,560802.513,4796643.087,In Gabia_4,,,In Gabia_4,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
50011,1048,5040,108,1,561374.68,4797043.59,0.00,2020-Sep-03 18:41:56.000,561378.55,4797043.79,38,1048/5040,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:03:26""",561459.17,4797098,LANDING,C:\SpiceRack\20200904\positions_log.csv,561378.269,4797046.361,Surface,,,Surface,OK,20200914,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
50003,1048,5008,113,1,560719.36,4796584.73,0.00,2020-Sep-03 18:41:55.000,560724.85,4796582.47,40,1048/5008,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:42:32""",560805.24,4796641.73,LANDING,C:\SpiceRack\20200904\positions_log.csv,560725.489,4796585.824,In Gabia_4,,,In Gabia_4,OK,20200912,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
50012,1048,5044,114,1,561456.6,4797100.95,0.00,2020-Sep-03 18:41:56.000,561459.17,4797098,40,1048/5044,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:02:45""",561444.84,4797094.82,NULL,C:\SpiceRack\20200904\positions_log.csv,561460.668,4797101.438,Fishermen,,,Fishermen,OK,20200912,Front truster HS,13-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
50008,1048,5028,116,1,561128.94,4796871.52,0.00,2020-Sep-03 18:41:55.000,561130.76,4796871.98,39,1048/5028,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,561129.58,4796872.38,In Gabia_4,,,In Gabia_4,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
50006,1048,5020,127,1,560965.11,4796756.81,0.00,2020-Sep-03 18:41:55.000,560966.27,4796753.71,38,1048/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:49:18""",561048.61,4796808.77,LANDING,C:\SpiceRack\20200904\positions_log.csv,560964.118,4796754.465,In Gabia_4,,,In Gabia_4,OK,20200912,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
50007,1048,5024,133,1,561047.02,4796814.16,0.00,2020-Sep-03 18:41:55.000,561048.61,4796808.77,40,1048/5024,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:51:16""",561130.94,4796871.76,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561049.771,4796813.181,In Gabia_4,,,In Gabia_4,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
50013,1048,5048,142,1,561538.51,4797158.31,0.00,2020-Sep-03 18:41:56.000,0,0,0,1048/5048,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:06:25""",561633.31,4797220.15,LANDED,C:\SpiceRack\20200904\positions_log.csv,,,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
50017,1048,5064,148,1,561866.17,4797387.74,0.00,2020-Sep-03 18:41:56.000,561871.62,4797384.94,40,1048/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:15:07""",561948.3,4797442.48,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561870.272,4797388.384,Surface,,,Surface,OK,20200912,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
50018,1048,5068,151,1,561948.09,4797445.1,0.00,2020-Sep-03 18:41:56.000,561948.87,4797442,40,1048/5068,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:18:00""",562019.18,4797502.75,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561951.805,4797447.679,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
50015,1048,5056,171,1,561702.34,4797273.02,0.00,2020-Sep-03 18:41:56.000,561707.62,4797273.05,38,1048/5056,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:12:40""",561786.59,4797327.81,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561706.482,4797274.799,Surface,,,Surface,OK,20200912,,15-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
50001,1048,5000,183,1,560555.53,4796470.02,0.00,2020-Sep-03 18:41:55.000,560558.73,4796466.74,40,1048/5000,0.0,150.20,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:40:08""",560644.57,4796524.63,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560558.708,4796469.609,In Gabia_4,,,In Gabia_4,OK,20200912,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
50019,1048,5072,191,1,562030.01,4797502.46,0.00,2020-Sep-03 18:41:56.000,562018.97,4797502.9,39,1048/5072,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:17:44""",562132.21,4797564.9,LANDING,C:\SpiceRack\20200904\positions_log.csv,562021.638,4797502.011,Surface,,,Surface,OK,20200913,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
50002,1048,5004,195,1,560637.45,4796527.38,0.00,2020-Sep-03 18:41:55.000,560644.22,4796524.88,40,1048/5004,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:55,247184155,"""2020-09-03 15:41:15""",560724.53,4796582.18,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560643.425,4796528.087,In Gabia_4,,,In Gabia_4,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
50014,1048,5052,200,1,561620.43,4797215.67,0.00,2020-Sep-03 18:41:56.000,561633.31,4797220.15,40,1048/5052,0.0,141.70,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:07:29""",561707.38,4797272.18,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561633.625,4797219.467,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
50016,1048,5060,201,1,561784.26,4797330.38,0.00,2020-Sep-03 18:41:56.000,561787.72,4797329.12,39,1048/5060,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 16:13:44""",561871.62,4797384.94,LANDING,C:\SpiceRack\20200904\positions_log.csv,561788.421,4797330.933,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
50020,1048,5076,206,1,562111.92,4797559.81,0.00,2020-Sep-03 18:41:56.000,562132.21,4797564.9,40,1048/5076,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,562131.838,4797568.01,Surface,,,Surface,OK,20200914,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
50009,1048,5032,210,1,561210.85,4796928.88,0.00,2020-Sep-03 18:41:56.000,0,0,0,1048/5032,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,,,,,,561196.164,4796930.859,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
60017,1060,5064,5,1,562038.25,4797141.99,0.00,2020-Sep-03 18:41:57.000,562032.52,4797142.46,41,1060/5064,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 17:03:58""",562114.01,4797201.91,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562034.626,4797139.477,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60016,1060,5060,6,1,561956.33,4797084.64,0.00,2020-Sep-03 18:41:57.000,561954.44,4797089.04,41,1060/5060,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 17:05:09""",562032.39,4797142.81,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561954.287,4797085.005,Surface failed,,,Surface failed,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
60015,1060,5056,17,1,561874.42,4797027.28,0.00,2020-Sep-03 18:41:57.000,561870.29,4797030.43,41,1060/5056,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 17:06:25""",561954.44,4797089.04,LANDING,C:\SpiceRack\20200904\positions_log.csv,561871.367,4797029.29,in Gabia_5,,,in Gabia_5,OK,20200914,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60007,1060,5024,23,1,561219.1,4796568.42,0.00,2020-Sep-03 18:41:56.000,561211.31,4796571.23,41,1060/5024,0.0,343.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:31:13""",561295.77,4796627.79,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561215.18,4796566.522,Surface,,,Surface,OK,20200913,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
60020,1060,5076,25,1,562283.99,4797314.07,0.00,2020-Sep-03 18:41:57.000,562278.7,4797316.4,39,1060/5076,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562280.32,4797312.616,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60019,1060,5072,26,1,562202.08,4797256.71,0.00,2020-Sep-03 18:41:57.000,0,0,0,1060/5072,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 17:00:27""",562277.25,4797318.15,PMU-A,C:\SpiceRack\20200904\positions_log.csv,562171.926,4797287.809,Surface,,,Surface,OK,20200913,Eastern Egg HS,13-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
60022,1060,5084,27,1,562447.82,4797428.78,0.00,2020-Sep-03 18:41:57.000,562441.18,4797433.37,41,1060/5084,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 16:40:59""",562594.05,4797574.21,NULL,C:\SpiceRack\20200904\positions_log.csv,562444.789,4797429.978,in Gabia_5,,,in Gabia_5,OK,20200913,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60023,1060,5088,30,1,562529.74,4797486.14,0.00,2020-Sep-03 18:41:57.000,0,0,0,1060/5088,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 16:37:47""",562605.01,4797544.81,LANDING,C:\SpiceRack\20200904\positions_log.csv,562594.718,4797574.544,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
60024,1060,5092,31,1,562611.65,4797543.5,0.00,2020-Sep-03 18:41:57.000,562605.01,4797544.81,41,1060/5092,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 16:38:06""",562715.12,4797714.73,NULL,C:\SpiceRack\20200904\positions_log.csv,562609.086,4797540.06,in Gabia_5,,,in Gabia_5,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60025,1060,5096,32,1,562693.57,4797600.86,0.00,2020-Sep-03 18:41:57.000,0,0,0,1060/5096,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562714.895,4797711.251,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
60018,1060,5068,39,1,562120.16,4797199.35,0.00,2020-Sep-03 18:41:57.000,562114.43,4797200.67,41,1060/5068,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,562115.953,4797198.078,in Gabia_5,,,in Gabia_5,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60010,1060,5036,41,1,561464.84,4796740.49,0.00,2020-Sep-03 18:41:56.000,561461.77,4796737.26,40,1060/5036,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:20:53""",561540.86,4796800.29,LANDED,C:\SpiceRack\20200904\positions_log.csv,561462.714,4796738.599,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60004,1060,5012,50,1,560973.35,4796396.34,0.00,2020-Sep-03 18:41:56.000,560967.85,4796398.88,41,1060/5012,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:38:41""",561051.42,4796457.21,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560971,4796394.574,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60014,1060,5052,65,1,561792.5,4796969.92,0.00,2020-Sep-03 18:41:57.000,0,0,0,1060/5052,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 17:14:51""",561870.46,4797030.51,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561838.329,4797028.333,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
60013,1060,5048,66,1,561710.59,4796912.56,0.00,2020-Sep-03 18:41:56.000,561707.37,4796915.04,38,1060/5048,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-04 14:33:09""",-1000,-1000,NULL,C:\SpiceRack\20200904\positions_log.csv,561707.996,4796913.517,in Gabia_5,,,in Gabia_5,OK,20200914,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60021,1060,5080,80,1,562365.91,4797371.42,0.00,2020-Sep-04 10:54:00.000,562368.3,4797368.8,0,1060/5080,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,"""2020-09-03 16:40:40""",562441.18,4797433.37,LANDING,C:\SpiceRack\20200904\positions_log.csv,562368.733,4797370.963,Manta_12614,Manta_12614,,Manta_12614,,,,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
60002,1060,5004,81,1,560809.52,4796281.63,0.00,2020-Sep-03 18:41:56.000,560806.44,4796286.11,41,1060/5004,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:41:15""",560888.53,4796340.44,LANDED,C:\SpiceRack\20200904\positions_log.csv,560807.441,4796280.46,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
60006,1060,5020,85,1,561137.18,4796511.06,0.00,2020-Sep-03 18:41:56.000,561132.91,4796511.38,41,1060/5020,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:32:40""",561211.31,4796571.23,LANDED,C:\SpiceRack\20200904\positions_log.csv,561133.24,4796507.219,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60009,1060,5032,101,1,561382.93,4796683.13,0.00,2020-Sep-03 18:41:56.000,561380.32,4796687.55,41,1060/5032,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:28:27""",561462.29,4796737.23,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561381.595,4796683.86,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
60005,1060,5016,121,1,561055.27,4796453.7,0.00,2020-Sep-03 18:41:56.000,561052.07,4796456.34,40,1060/5016,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:33:53""",561132.91,4796511.38,LANDING,C:\SpiceRack\20200904\positions_log.csv,561054.43,4796453.122,in Gabia_5,,,in Gabia_5,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
60001,1060,5000,123,1,560727.6,4796224.27,0.00,2020-Sep-03 18:41:56.000,560722.61,4796222.71,38,1060/5000,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:42:29""",560806.31,4796286.35,PMU-A,C:\SpiceRack\20200904\positions_log.csv,560724.094,4796221.694,in Gabia_5,,,in Gabia_5,OK,20200914,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60008,1060,5028,145,1,561301.01,4796625.78,0.00,2020-Sep-03 18:41:56.000,561295.8,4796627.66,41,1060/5028,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:29:50""",561380.32,4796687.55,LANDING,C:\SpiceRack\20200904\positions_log.csv,561298.63,4796625.635,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
60012,1060,5044,176,1,561628.67,4796855.21,0.00,2020-Sep-03 18:41:56.000,561624.82,4796856.33,40,1060/5044,0.0,-10.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:18:09""",561705.89,4796919.07,PMU-A,C:\SpiceRack\20200904\positions_log.csv,561625.079,4796852.846,Fishermen,,,Fishermen,OK,20200912,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
60003,1060,5008,178,1,560891.44,4796338.99,0.00,2020-Sep-03 18:41:56.000,560888.53,4796340.44,40,1060/5008,0.0,328.80,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:39:55""",560967.85,4796398.88,LANDING,C:\SpiceRack\20200904\positions_log.csv,560889.279,4796336.938,Surface failed,,,Surface failed,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
60011,1060,5040,190,1,561546.76,4796797.85,0.00,2020-Sep-03 18:41:56.000,561540.86,4796800.29,41,1060/5040,0.0,326.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:56,247184156,"""2020-09-03 17:19:17""",561624.82,4796856.33,LANDING,C:\SpiceRack\20200904\positions_log.csv,561542.942,4796795.61,in Gabia_5,,,in Gabia_5,OK,20200913,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70019,1072,5072,8,1,562374.15,4797010.96,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5072,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562377.226,4797012.3,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70020,1072,5076,14,1,562456.07,4797068.32,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5076,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562462.045,4797062.995,Gabia missed,,,Surface,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
70001,1072,5000,75,1,560899.68,4795978.53,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5000,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,560902.441,4795977.074,in Gabia_5,,,in Gabia_5,OK,20200914,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70002,1072,5004,96,1,560981.59,4796035.88,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5004,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,560985.309,4796034.419,Surface,,,Surface,OK,20200914,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
70021,1072,5080,117,1,562537.98,4797125.68,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5080,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562534.141,4797123.61,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70025,1072,5096,126,1,562865.64,4797355.11,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5096,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562868.786,4797352.891,in Gabia_5,,,in Gabia_5,OK,20200913,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70022,1072,5084,130,1,562619.9,4797183.04,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5084,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562626.12,4797180.707,Surface,,,Surface,OK,20200914,,,,10,,,,,,,,,,,,,,,,,,,,,,,,
70024,1072,5092,135,1,562783.73,4797297.75,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5092,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562789.217,4797297.665,Surface,,,Surface,OK,20200913,Eastern Egg and front truster HS,13-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
70018,1072,5068,137,1,562292.24,4796953.61,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5068,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562305.188,4796953.401,Fished,EE_20200912_01,,Fished,OK,20200913,Eastern Egg and front truster HS,13-Sep,,-10,,,,,,,,,,,,,,,,,,,,,,,,
70023,1072,5088,140,1,562701.81,4797240.39,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5088,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562711.023,4797232.686,Surface,,,Surface,OK,20200913,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
70010,1072,5036,144,1,561636.91,4796494.75,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5036,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561631.634,4796506.086,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70007,1072,5024,150,1,561391.17,4796322.67,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5024,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561395.994,4796324.225,in Gabia_5,,,in Gabia_5,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70011,1072,5040,158,1,561718.83,4796552.1,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5040,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561587.348,4796631.849,Surface failed,,,Surface failed,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
70016,1072,5060,160,1,562128.41,4796838.89,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5060,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562133.38,4796838.645,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70017,1072,5064,161,1,562210.32,4796896.25,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5064,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562123.354,4796853.919,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
70013,1072,5048,162,1,561882.66,4796666.82,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5048,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561802.101,4796622.036,Fishermen,,,Fishermen,OK,20200912,Fishing boat,13-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
70015,1072,5056,167,1,562046.49,4796781.53,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5056,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562063.163,4796784.615,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70014,1072,5052,168,1,561964.58,4796724.18,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5052,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561958.186,4796728.041,Surface,,,Surface,OK,20200913,,15-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
70008,1072,5028,173,1,561473.08,4796380.03,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5028,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561473.532,4796379.241,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70006,1072,5020,186,1,561309.25,4796265.31,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5020,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561308.411,4796262.927,Surface,,,Surface,OK,20200913,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
70009,1072,5032,187,1,561555,4796437.39,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5032,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561557.082,4796438.134,in Gabia_5,,,in Gabia_5,OK,20200913,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
70012,1072,5044,189,1,561800.74,4796609.46,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5044,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561803.92,4796606.764,in Gabia_5,,,in Gabia_5,OK,20200913,,15-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70003,1072,5008,193,1,561063.51,4796093.24,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5008,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,560998.126,4796047.935,in Gabia_5,,,in Gabia_5,OK,20200913,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
70004,1072,5012,202,1,561145.42,4796150.6,0.00,2020-Sep-03 18:41:57.000,0,0,0,1072/5012,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561145.75,4796151.973,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
70005,1072,5016,205,1,561227.34,4796207.96,0.00,2020-Sep-04 09:28:00.000,561231.5,479622,0,1072/5016,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561225.176,4796206.454,ROV,ROV_20200915_08,00:13,Manta_4071,,,Battery HS,,,-30,,,,,,,,,,,,,,,,,,,,,,,,
80008,1084,5028,3,1,561645.16,4796134.28,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5028,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561638.939,4796134.916,In Gabia_6,,,In Gabia_6,OK,20200914,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
80009,1084,5032,9,1,561727.07,4796191.64,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5032,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561725.473,4796192.107,In Gabia_6,,,In Gabia_6,OK,20200913,Eastern Egg HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
80005,1084,5016,20,1,561399.41,4795962.21,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5016,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561452.337,4796018.81,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80012,1084,5044,22,1,561972.82,4796363.71,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5044,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561966.857,4796366.027,Surface,,,Surface,OK,20200914,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
80014,1084,5052,33,1,562136.65,4796478.43,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5052,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562135.671,4796478.783,Fishermen,,,Fishermen,OK,20200912,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80015,1084,5056,38,1,562218.56,4796535.79,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5056,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562208.39,4796531.419,Fishermen,,,Fishermen,OK,20200912,Fishing boat,13-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80013,1084,5048,42,1,562054.73,4796421.07,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5048,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562051.96,4796423.01,Surface,,,Surface,OK,20200914,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
80001,1084,5000,48,1,561071.75,4795732.78,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5000,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561070.56,4795734.655,In Gabia_6,,,In Gabia_6,OK,20200913,,14-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
80016,1084,5060,49,1,562300.48,4796593.15,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5060,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562365.641,4796656.326,Fishermen,,,Fishermen,OK,20200913,Fishing boat,13-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80024,1084,5092,64,1,562955.8,4797052.01,0.00,2020-Sep-03 18:41:58.000,0,0,0,1084/5092,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:58,247184158,,,,,,562954.669,4797050.97,Fishermen,,,Fishermen,OK,20200912,,15-Sep,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80019,1084,5072,69,1,562546.22,4796765.22,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5072,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562544.851,4796766.816,Surface failed,,,Surface failed,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
80020,1084,5076,70,1,562628.14,4796822.58,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5076,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562626.102,4796821.835,Surface,,,Surface,OK,20200914,,14-Sep,,10,,,,,,,,,,,,,,,,,,,,,,,,
80007,1084,5024,74,1,561563.24,4796076.93,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5024,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561623.984,4796133.78,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
80021,1084,5080,76,1,562710.05,4796879.93,0.00,2020-Sep-03 18:41:58.000,0,0,0,1084/5080,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:58,247184158,,,,,,562708.586,4796880.264,No com,,,No com,,,,,,-10,,,,,,,,,,,,,,,,,,,,,,,,
80022,1084,5084,77,1,562791.97,4796937.29,0.00,2020-Sep-03 18:41:58.000,0,0,0,1084/5084,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:58,247184158,,,,,,562788.733,4796937.844,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80023,1084,5088,82,1,562873.89,4796994.65,0.00,2020-Sep-03 18:41:58.000,0,0,0,1084/5088,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:58,247184158,,,,,,562868.382,4796995.728,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80006,1084,5020,97,1,561481.33,4796019.57,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5020,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561479.344,4796019.992,In Gabia_6,,,In Gabia_6,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
80018,1084,5068,105,1,562464.31,4796707.86,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5068,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562460.123,4796712.156,In Gabia_6,,,In Gabia_6,OK,20200913,Front truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
80010,1084,5036,106,1,561808.99,4796249,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5036,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561807.981,4796250.177,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80003,1084,5008,111,1,561235.58,4795847.5,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5008,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561234.892,4795851.971,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80017,1084,5064,112,1,562382.39,4796650.5,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5064,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,562381.774,4796653.825,In Gabia_6,,,In Gabia_6,OK,20200914,,,,20,,,,,,,,,,,,,,,,,,,,,,,,
80004,1084,5012,118,1,561317.5,4795904.85,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5012,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561319.96,4795905.44,No wake Up,,,No wake Up,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80011,1084,5040,120,1,561890.9,4796306.36,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5040,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561887.928,4796309.881,Fishermen,,,Fishermen,OK,20200912,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
80025,1084,5096,122,1,563037.72,4797109.36,0.00,2020-Sep-03 18:41:58.000,0,0,0,1084/5096,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:58,247184158,,,,,,563034.171,4797110.504,In Gabia_6,,,In Gabia_6,OK,20200913,Left rear truster HS,13-Sep,,20,,,,,,,,,,,,,,,,,,,,,,,,
80002,1084,5004,163,1,561153.67,4795790.14,0.00,2020-Sep-03 18:41:57.000,0,0,0,1084/5004,0.0,0.00,,0,0,0,0.0,0,,,,,,,,,,,,,,,,3-Sep-20,247,6:41:57,247184157,,,,,,561147.742,4795788.78,No take off,,,No take off,,,,,,-20,,,,,,,,,,,,,,,,,,,,,,,,
99999,9999,9999,110,9,,,,,,,,9999/9999,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Never deployed,,,,,,Never deployed and mere porteuse,14-Sep,,#N/A,,,,,,,,,,,,,,,,,,,,,,,,
99999,9999,9999,62,9,,,,,,,,9999/9999,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Never deployed,,,,,,Never deployed,13-Sep,,#N/A,,,,,,,,,,,,,,,,,,,,,,,,
1 1 2 Sete-Pilot-20200903.csv 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 47 All commands failed -10 97 196 1
2 Comment_ori
3 FAUX Deployment Check FIX Recovery Check FIX Last message from position log 145 Fished -10
4 concat Line Point NodeCode Index Preplot Easting Preplot Northing Preplot Depth Aslaid Time Aslaid Easting Aslaid Northing Aslaid Depth Aslaid Tide Offset Aslaid Azimuth Recovered Time Recovered Easting Recovered Northing Recovered Depth Recovered Tide Offset Recovered Azimuth PreplotToAslaidDistance PreplotToAslaidBearing AslaidToRecoveredDistance AslaidToRecoveredBearing RecoveredToPreplotDistance RecoveredToPreplotBearing PreplotToAslaidAlongTrack PreplotToAslaidCrossTrack RecoveredToPreplotAlongTrack RecoveredToPreplotCrossTrack AslaidToRecoveredAlongTrack AslaidToRecoveredCrossTrack DeployedComments RecoveredComments Flag Date JD Time JDTIME time X_Pos Y_Pos Status File X_Comp Y_Comp Comment comment_bis duration Comment_ori log_download date comment_pickup date sent onshore Comment Fishermen -20
5 10001 1000 5000 4 1 559867.24 4797453 0.00 2020-Sep-03 18:41:52.000 0 0 0 1000/5000 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:10:09" 559951.24 4797507.26 PMU-A C:\SpiceRack\20200904\positions_log.csv 559869.849 4797451.921 In Gabia_1 In Gabia_1 OK 20200912 20 Gabia missed 10
6 10002 1000 5004 10 1 559949.16 4797510.36 0.00 2020-Sep-03 18:41:52.000 0 0 0 1000/5004 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:10:51" 560035.89 4797566.14 LANDING C:\SpiceRack\20200904\positions_log.csv 559950.057 4797509.16 In Gabia_1 In Gabia_1 OK 20200912 20 In Gabia_1 20
7 10003 1000 5008 11 1 560031.07 4797567.72 0.00 2020-Sep-03 18:41:52.000 0 0 0 1000/5008 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:15:39" 560117.19 4797620.55 NULL C:\SpiceRack\20200904\positions_log.csv 560035.816 4797565.643 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 In Gabia_2 20
8 10004 1000 5012 15 1 560112.99 4797625.07 0.00 2020-Sep-03 18:41:52.000 560116.82 4797621.72 37 1000/5012 0.0 147.40 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 560115.915 4797622.84 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 In Gabia_3 20
9 10006 1000 5020 37 1 560276.82 4797739.79 0.00 2020-Sep-03 18:41:52.000 560284.01 4797736.15 35 1000/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:39:33" 560367.98 4797796.99 LANDED C:\SpiceRack\20200904\positions_log.csv 560281.716 4797739.475 In Gabia_2 In Gabia_2 OK 20200912 Eastern Egg HS 13-Sep 20 In Gabia_4 20
10 10007 1000 5024 40 1 560358.73 4797797.15 0.00 2020-Sep-03 18:41:52.000 560367.98 4797796.99 36 1000/5024 0.0 141.70 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:48:23" 560441.57 4797848.22 NULL C:\SpiceRack\20200904\positions_log.csv 560367.603 4797799.043 Surface Surface OK 20200912 14-Sep 10 in Gabia_5 20
11 10008 1000 5028 43 1 560440.65 4797854.5 0.00 2020-Sep-03 18:41:52.000 560439.27 4797850.52 -10 1000/5028 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:45:47" 560527.58 4797908.74 LANDED C:\SpiceRack\20200904\positions_log.csv 560452.745 4797859.883 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 In Gabia_6 20
12 10009 1000 5032 44 1 560522.56 4797911.86 0.00 2020-Sep-03 18:41:52.000 560527.58 4797908.74 37 1000/5032 0.0 144.60 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:45:44" 560599.66 4797961.54 LANDED C:\SpiceRack\20200904\positions_log.csv 560527.112 4797910.961 In Gabia_1 In Gabia_1 OK 20200912 20 Manta_10484 -30
13 10010 1000 5036 45 1 560604.48 4797969.22 0.00 2020-Sep-03 18:41:52.000 560599.66 4797961.54 37 1000/5036 0.0 133.20 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:25:50" 560689.12 4798023.32 PMU-A C:\SpiceRack\20200904\positions_log.csv 560597.021 4797963.004 In Gabia_1 In Gabia_1 OK 20200912 Front truster HS 13-Sep 20 Manta_12025 -30
14 10011 1000 5040 52 1 560686.39 4798026.58 0.00 2020-Sep-03 18:41:52.000 560687.96 4798024.23 35 1000/5040 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:27:20" 560773.34 4798084.4 LANDING C:\SpiceRack\20200904\positions_log.csv 560687.314 4798026.735 Fishermen Fishermen OK 20200912 Fishing boat 13-Sep -20 Manta_1240 -30
15 10012 1000 5044 54 1 560768.31 4798083.93 0.00 2020-Sep-03 18:41:52.000 560773.34 4798084.4 37 1000/5044 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:28:52" 560853.11 4798142.13 PMU-A C:\SpiceRack\20200904\positions_log.csv 560771.925 4798086.655 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 Manta_12443 -30
16 10013 1000 5048 56 1 560850.22 4798141.29 0.00 2020-Sep-03 18:41:52.000 0 0 0 1000/5048 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:30:32" 560936.14 4798197.59 LANDING C:\SpiceRack\20200904\positions_log.csv 560852.9 4798141.947 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 Manta_12614 -30
17 10014 1000 5052 63 1 560932.14 4798198.65 0.00 2020-Sep-03 18:41:52.000 560936.14 4798197.59 37 1000/5052 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:33:50" 561013.53 4798249.08 LANDED C:\SpiceRack\20200904\positions_log.csv 560934.867 4798201.855 Fishermen Fishermen OK 20200913 Fishing boat 13-Sep -20 Manta_2753 -30
18 10015 1000 5056 67 1 561014.05 4798256.01 0.00 2020-Sep-03 18:41:52.000 561013.53 4798249.08 37 1000/5056 0.0 138.90 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:39:55" 561099.49 4798310.67 PMU-A C:\SpiceRack\20200904\positions_log.csv 561012.719 4798250.397 In Gabia_1 In Gabia_1 OK 20200914 20 Manta_4071 -30
19 10016 1000 5060 72 1 561095.97 4798313.36 0.00 2020-Sep-03 18:41:52.000 561097.53 4798313.11 35 1000/5060 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:41:21" 561176.33 4798369.57 PMU-A C:\SpiceRack\20200904\positions_log.csv 561096.612 4798314.914 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 Navigation issue -10
20 10017 1000 5064 73 1 561177.88 4798370.72 0.00 2020-Sep-03 18:41:52.000 561181.16 4798371.55 34 1000/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:45:18" 561177.18 4798341.87 PMU-A C:\SpiceRack\20200904\positions_log.csv 561178.199 4798370.267 In Gabia_1 In Gabia_1 OK 20200912 14-Sep 20 No com -10
21 10018 1000 5068 78 1 561259.8 4798428.08 0.00 2020-Sep-03 18:41:52.000 0 0 0 1000/5068 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:54:44" 561342.98 4798481.82 NULL C:\SpiceRack\20200904\positions_log.csv 561180.452 4798342.119 ROV ROV_20200914_01 0:29 Gabia missed Fail 10 No take off -20
22 10019 1000 5072 84 1 561341.71 4798485.44 0.00 2020-Sep-03 18:41:52.000 561343.68 4798482.74 -10 1000/5072 0.0 141.70 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 10:56:07" 561423.11 4798539.88 NULL C:\SpiceRack\20200904\positions_log.csv 561344.171 4798485.89 Fishermen Fishermen Fail No Com, Electronic HS 14-Sep -20 No wake Up -20
23 10020 1000 5076 87 1 561423.63 4798542.8 0.00 2020-Sep-03 18:41:52.000 561423 4798540.05 37 1000/5076 0.0 127.60 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-04 12:50:32" 561613.17 4796861.24 NULL C:\SpiceRack\20200904\positions_log.csv 561408.546 4798520.607 In Gabia_1 In Gabia_1 OK 20200913 Eastern Egg and front truster HS 13-Sep 20 Surface 10
24 10021 1000 5080 88 1 561505.54 4798600.15 0.00 2020-Sep-03 18:41:52.000 561508.79 4798599.75 37 1000/5080 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-04 12:50:32" 561636.82 4796505.46 NULL C:\SpiceRack\20200904\positions_log.csv 561508.137 4798603.16 In Gabia_1 In Gabia_1 OK 20200912 Eastern Egg and front truster HS 13-Sep 20 Surface failed -10
25 10022 1000 5084 91 1 561587.46 4798657.51 0.00 2020-Sep-03 18:41:52.000 561590.22 4798654.3 37 1000/5084 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 11:06:54" 561668.07 4798713.18 PMU-A C:\SpiceRack\20200904\positions_log.csv 561589.107 4798657.864 ROV ROV_20200914_02 0:15 No wake Up OK 15-Sep -20
26 10023 1000 5088 99 1 561669.37 4798714.87 0.00 2020-Sep-03 18:41:52.000 561674.16 4798725.83 35 1000/5088 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 11:08:59" 561755.37 4798770.24 PMU-A C:\SpiceRack\20200904\positions_log.csv 561671.21 4798716.041 In Gabia_1 In Gabia_1 OK 20200912 20
27 10024 1000 5092 100 1 561751.29 4798772.23 0.00 2020-Sep-03 18:41:53.000 561755.58 4798770.13 37 1000/5092 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:10:57" 561837.23 4798830.21 LANDED C:\SpiceRack\20200904\positions_log.csv 561754.318 4798774.606 In Gabia_1 In Gabia_1 OK 20200912 20
28 10025 1000 5096 102 1 561833.2 4798829.58 0.00 2020-Sep-03 18:41:53.000 561837.23 4798830.21 37 1000/5096 0.0 147.40 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 561837.437 4798834.59 In Gabia_1 In Gabia_1 OK 20200914 20
29 10005 1000 5016 156 1 560194.9 4797682.43 0.00 2020-Sep-04 12:16:00.000 560196.9 4796699.2 0 1000/5016 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:52 247184152 "2020-09-03 09:36:53" 560283.82 4797736.63 PMU-A C:\SpiceRack\20200904\positions_log.csv 560198.796 4797676.377 Manta_10484 Manta_10484 Manta_10484 -30
30 20002 1012 5004 2 1 560121.23 4797264.61 0.00 2020-Sep-03 18:41:53.000 560115.92 4797263.54 37 1012/5004 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:46:45" 560198.22 4797324.27 LANDING C:\SpiceRack\20200904\positions_log.csv 560117.038 4797260.288 In Gabia_2 In Gabia_2 OK 20200912 14-Sep 20
31 20001 1012 5000 28 1 560039.31 4797207.25 0.00 2020-Sep-03 18:41:53.000 0 0 0 1012/5000 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:48:24" 560115.92 4797263.54 LANDING C:\SpiceRack\20200904\positions_log.csv 560085.541 4797284.652 No com No com -10
32 20011 1012 5040 90 1 560858.46 4797780.83 0.00 2020-Sep-04 11:56:00.000 560854.7 4797782.2 0 1012/5040 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:02:22" 560848.72 4797786.56 NAVIG C:\SpiceRack\20200904\positions_log.csv 560856.964 4797781.978 ROV ROV_20200914_07 0:28 Manta_2753 Fail Right rear truster twisted, left rear truster locked 15-Sep -30
33 20025 1012 5096 93 1 562005.28 4798583.84 0.00 2020-Sep-03 18:41:54.000 562004.69 4798584.18 33 1012/5096 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:30:48" 560215.02 4796956.67 LANDED C:\SpiceRack\20200904\positions_log.csv 562005.278 4798583.838 In Gabia_2 In Gabia_2 OK 20200914 20
34 20024 1012 5092 94 1 561923.36 4798526.48 0.00 2020-Sep-03 18:41:54.000 561922.82 4798527.69 38 1012/5092 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 11:21:12" 562000.87 4798585.11 PMU-A C:\SpiceRack\20200904\positions_log.csv 561924.089 4798526.228 In Gabia_2 In Gabia_2 OK 20200912 20
35 20003 1012 5008 103 1 560203.14 4797321.97 0.00 2020-Sep-03 18:41:53.000 560198.22 4797324.27 37 1012/5008 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:44:59" 560278.32 4797379.55 PMU-A C:\SpiceRack\20200904\positions_log.csv 560200.097 4797319.715 Surface Surface OK 20200912 14-Sep 10
36 20004 1012 5012 104 1 560285.06 4797379.33 0.00 2020-Sep-03 18:41:53.000 560278.76 4797378.81 37 1012/5012 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:43:39" 560361.72 4797437.23 LANDING C:\SpiceRack\20200904\positions_log.csv 560280.22 4797375.689 ROV ROV_20200914_05 0:45 All commands failed Right Rear truster HS -10
37 20023 1012 5088 109 1 561841.45 4798469.12 0.00 2020-Sep-03 18:41:53.000 561837.73 4798467.75 35 1012/5088 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:23:33" 561921.31 4798530.37 PMU-A C:\SpiceRack\20200904\positions_log.csv 561838.94 4798465.796 In Gabia_2 In Gabia_2 OK 20200914 15-Sep 20
38 20022 1012 5084 115 1 561759.53 4798411.76 0.00 2020-Sep-03 18:41:53.000 561769.11 4798417.87 35 1012/5084 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:27:00" 561177.03 4798346.62 INIT C:\SpiceRack\20200904\positions_log.csv 561768.498 4798416.55 Gabia missed Gabia missed 10
39 20005 1012 5016 124 1 560366.97 4797436.68 0.00 2020-Sep-03 18:41:53.000 560361.72 4797437.23 37 1012/5016 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:23:00" 560443.65 4797502.41 LANDING C:\SpiceRack\20200904\positions_log.csv 560364.649 4797432.797 ROV ROV_20200914_06 0:15 No wake Up OK -20
40 20007 1012 5024 128 1 560530.8 4797551.4 0.00 2020-Sep-03 18:41:53.000 560529.16 4797555.9 36 1012/5024 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:19:39" 560606.9 4797608.23 PMU-A C:\SpiceRack\20200904\positions_log.csv 560528.912 4797553.528 In Gabia_2 In Gabia_2 OK 20200912 14-Sep 20
41 20021 1012 5080 131 1 561677.62 4798354.41 0.00 2020-Sep-03 18:41:53.000 0 0 0 1012/5080 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:24:49" 561765.91 4798418.77 PMU-A C:\SpiceRack\20200904\positions_log.csv 561772.233 4798451.29 ROV ROV_20200914_03 0:25 Navigation issue OK 15-Sep -10
42 20020 1012 5076 134 1 561595.7 4798297.05 0.00 2020-Sep-03 18:41:53.000 561590.55 4798294.8 36 1012/5076 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:26:54" 561774.36 4798449.79 PMU-A C:\SpiceRack\20200904\positions_log.csv 561590.772 4798294.037 In Gabia_2 In Gabia_2 OK 20200912 20
43 20019 1012 5072 136 1 561513.79 4798239.69 0.00 2020-Sep-03 18:41:53.000 561506.4 4798241.76 38 1012/5072 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:40:24" 561587.63 4798297.88 PMU-A C:\SpiceRack\20200904\positions_log.csv 561506.805 4798240.623 ROV ROV_20200914_04 0:08 Navigation issue OK -10
44 20018 1012 5068 143 1 561431.87 4798182.33 0.00 2020-Sep-03 18:41:53.000 561426.34 4798183.54 38 1012/5068 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:42:13" 561506.4 4798241.76 LANDING C:\SpiceRack\20200904\positions_log.csv 561429.539 4798180.879 In Gabia_2 In Gabia_2 OK 20200912 20
45 20017 1012 5064 147 1 561349.96 4798124.98 0.00 2020-Sep-03 18:41:53.000 561344.38 4798127.78 37 1012/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:48:11" 561182.12 4798346.76 PMU-A C:\SpiceRack\20200904\positions_log.csv 561346.795 4798122.986 Surface Surface OK 20200912 Eastern Egg and front truster HS 13-Sep 10
46 20006 1012 5020 172 1 560448.89 4797494.04 0.00 2020-Sep-03 18:41:53.000 560443.65 4797502.41 37 1012/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:21:02" 560527.65 4797555.29 PMU-A C:\SpiceRack\20200904\positions_log.csv 560445.974 4797492.292 ROV ROV_20200915_01 00:11 All commands failed OK -10
47 20016 1012 5060 175 1 561268.04 4798067.62 0.00 2020-Sep-03 18:41:53.000 561264.85 4798069.07 37 1012/5060 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:45:44" 561343.88 4798128.72 PMU-A C:\SpiceRack\20200904\positions_log.csv 561265.234 4798067.596 In Gabia_2 In Gabia_2 OK 20200912 Front truster HS 13-Sep 20
48 20008 1012 5028 177 1 560612.72 4797608.76 0.00 2020-Sep-03 18:41:53.000 560607.4 4797607.81 35 1012/5028 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:17:37" 560686.24 4797660.78 PMU-A C:\SpiceRack\20200904\positions_log.csv 560607.02 4797608.067 In Gabia_2 In Gabia_2 OK 20200912 14-Sep 20
49 20015 1012 5056 184 1 561186.13 4798010.26 0.00 2020-Sep-03 18:41:53.000 561179.92 4798010.28 38 1012/5056 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:48:11" 561264.81 4798069.27 PMU-A C:\SpiceRack\20200904\positions_log.csv 561181.484 4798006.14 In Gabia_2 In Gabia_2 OK 20200912 14-Sep 20
50 20014 1012 5052 188 1 561104.21 4797952.9 0.00 2020-Sep-03 18:41:53.000 561099.09 4797952.13 37 1012/5052 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:56:46" 561179.92 4798010.42 PMU-A C:\SpiceRack\20200904\positions_log.csv 561100.592 4797948.599 Fishermen Fishermen OK 20200914 14-Sep -20
51 20013 1012 5048 192 1 561022.3 4797895.55 0.00 2020-Sep-03 18:41:53.000 561017.51 4797898.16 38 1012/5048 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 11:58:48" 561097.92 4797954.02 PMU-A C:\SpiceRack\20200904\positions_log.csv 561019.934 4797894.387 In Gabia_2 In Gabia_2 OK 20200913 20
52 20012 1012 5044 198 1 560940.38 4797838.19 0.00 2020-Sep-03 18:41:53.000 0 0 0 1012/5044 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:00:21" 561017.51 4797898.16 LANDING C:\SpiceRack\20200904\positions_log.csv 560830.183 4797774.82 In Gabia_2 In Gabia_2 OK 20200912 20
53 20010 1012 5036 207 1 560776.55 4797723.47 0.00 2020-Sep-03 18:41:53.000 560771.05 4797725.37 38 1012/5036 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 560773.729 4797720.893 Surface Surface OK 20200912 15-Sep 10
54 20009 1012 5032 209 1 560694.63 4797666.12 0.00 2020-Sep-03 18:41:53.000 560686.06 4797660.8 38 1012/5032 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:53 247184153 "2020-09-03 12:15:39" 560771.05 4797725.37 LANDING C:\SpiceRack\20200904\positions_log.csv 560688.193 4797656.956 In Gabia_2 In Gabia_2 OK 20200913 15-Sep 20
55 30001 1024 5000 7 1 560211.39 4796961.51 0.00 2020-Sep-03 18:41:54.000 560215.02 4796956.67 38 1024/5000 0.0 136.10 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:32:17" 560298.03 4797014.52 PMU-A C:\SpiceRack\20200904\positions_log.csv 560213.45 4796961.015 In Gabia_3 In Gabia_3 OK 20200912 20
56 30016 1024 5060 12 1 561440.11 4797821.87 0.00 2020-Sep-03 18:41:54.000 561445.96 4797815.07 39 1024/5060 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-04 14:32:56" 561579.41 4797901.1 SLEEP C:\SpiceRack\20200904\positions_log.csv 561441.385 4797821.035 Surface Surface OK 20200912 10
57 30017 1024 5064 13 1 561522.03 4797879.23 0.00 2020-Sep-03 18:41:54.000 561581.63 4797895.28 38 1024/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-04 14:32:56" 561592.27 4797937.73 NULL C:\SpiceRack\20200904\positions_log.csv 561581.19 4797895.56 In Gabia_3 In Gabia_3 OK 20200912 20
58 30020 1024 5076 19 1 561767.77 4798051.3 0.00 2020-Sep-04 11:19:00.000 561769 4798051.8 0 1024/5076 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:20:27" 561875.34 4798085.45 PMU-A C:\SpiceRack\20200904\positions_log.csv 561765.567 4798052.117 ROV ROV_20200915_05 00:21 Manta_12025 -30
59 30015 1024 5056 21 1 561358.2 4797764.52 0.00 2020-Sep-03 18:41:54.000 561418.47 4797772.45 39 1024/5056 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:08:33" 561445.96 4797815.07 LANDING C:\SpiceRack\20200904\positions_log.csv 561418.361 4797772.865 In Gabia_3 In Gabia_3 OK 20200912 20
60 30002 1024 5004 34 1 560293.3 4797018.87 0.00 2020-Sep-03 18:41:54.000 560298.26 4797014.79 38 1024/5004 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:34:00" 560377.78 4797071.06 LANDING C:\SpiceRack\20200904\positions_log.csv 560294.922 4797020.2 In Gabia_3 In Gabia_3 OK 20200914 20
61 30003 1024 5008 46 1 560375.22 4797076.22 0.00 2020-Sep-03 18:41:54.000 560377.78 4797071.06 38 1024/5008 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:35:42" 560459.58 4797131.65 LANDING C:\SpiceRack\20200904\positions_log.csv 560377.035 4797077.075 In Gabia_3 In Gabia_3 OK 20200914 14-Sep 20
62 30018 1024 5068 55 1 561603.94 4797936.59 0.00 2020-Sep-03 18:41:54.000 561616 4797933.93 35 1024/5068 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:12:46" 561686.5 4797991.88 LANDING C:\SpiceRack\20200904\positions_log.csv 561615.628 4797934.243 Surface Surface Fail 20200912 10
63 30019 1024 5072 83 1 561685.86 4797993.95 0.00 2020-Sep-03 18:41:54.000 561686.5 4797991.88 39 1024/5072 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 561686.84 4797993.526 ROV ROV_20200915_04 No take off Easter Egg flashing but not released -20
64 30004 1024 5012 86 1 560457.13 4797133.58 0.00 2020-Sep-03 18:41:54.000 560459.58 4797131.65 38 1024/5012 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 560458.967 4797136.786 In Gabia_3 In Gabia_3 OK 20
65 30005 1024 5016 89 1 560539.05 4797190.94 0.00 2020-Sep-03 18:41:54.000 0 0 0 1024/5016 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:43:23" 560620.54 4797246.59 PMU-A C:\SpiceRack\20200904\positions_log.csv 560479.91 4797095.516 No com No com -10
66 30006 1024 5020 129 1 560620.96 4797248.3 0.00 2020-Sep-03 18:41:54.000 560621.9 4797245.96 37 1024/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:43:52" 560619.32 4797170.24 NAVIG C:\SpiceRack\20200904\positions_log.csv 560622.526 4797249.315 Gabia missed Gabia missed 10
67 30007 1024 5024 132 1 560702.88 4797305.65 0.00 2020-Sep-03 18:41:54.000 0 0 0 1024/5024 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:46:29" 560800.6 4797380.53 LANDING C:\SpiceRack\20200904\positions_log.csv 560689.369 4797241.869 ROV ROV_20200915_02 00:20 Navigation issue OK -10
68 30021 1024 5080 139 1 561849.69 4798108.66 0.00 2020-Sep-03 18:41:54.000 561875.68 4798085.53 38 1024/5080 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:20:59" 561932.8 4798165.33 PMU-A C:\SpiceRack\20200904\positions_log.csv 561875.649 4798086.767 In Gabia_3 In Gabia_3 OK 20200912 Front truster HS 13-Sep 20
69 30008 1024 5028 146 1 560784.79 4797363.01 0.00 2020-Sep-03 18:41:54.000 560800.6 4797380.53 38 1024/5028 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:47:04" 560783.34 4797363.17 PMU-A C:\SpiceRack\20200904\positions_log.csv 560785.95 4797365.636 In Gabia_3 In Gabia_3 OK 20200912 15-Sep 20
70 30022 1024 5084 149 1 561931.61 4798166.02 0.00 2020-Sep-03 18:41:54.000 0 0 0 1024/5084 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 18:03:58" 561082.14 4797637.81 INIT C:\SpiceRack\20200904\positions_log.csv 561932.877 4798166.765 In Gabia_3 In Gabia_3 OK 20200912 15-Sep 20
71 30009 1024 5032 152 1 560866.71 4797420.37 0.00 2020-Sep-03 18:41:54.000 0 0 0 1024/5032 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:49:12" 560950.07 4797473.85 LANDING C:\SpiceRack\20200904\positions_log.csv 560799.911 4797386.891 Surface Surface OK 20200912 15-Sep 10
72 30010 1024 5036 153 1 560948.62 4797477.73 0.00 2020-Sep-03 18:41:54.000 560950.07 4797473.85 38 1024/5036 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:55:49" 561032.79 4797532.38 LANDING C:\SpiceRack\20200904\positions_log.csv 560949.103 4797477.235 In Gabia_3 In Gabia_3 OK 20200913 Front truster HS 13-Sep 20
73 30023 1024 5088 154 1 562013.52 4798223.38 0.00 2020-Sep-03 18:41:54.000 562015.28 4798220.02 38 1024/5088 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:23:55" 562103.55 4798277.46 PMU-A C:\SpiceRack\20200904\positions_log.csv 562013.738 4798221.875 In Gabia_3 In Gabia_3 OK 20200912 15-Sep 20
74 30025 1024 5096 157 1 562177.35 4798338.09 0.00 2020-Sep-03 18:41:54.000 562182.94 4798336.65 39 1024/5096 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 15:28:56" 560382.14 4796717.02 PMU-A C:\SpiceRack\20200904\positions_log.csv 562184.064 4798339.728 Surface Surface OK 20200912 Front truster HS 13-Sep 10
75 30014 1024 5052 164 1 561276.28 4797707.16 0.00 2020-Sep-03 18:41:54.000 561280.89 4797703.67 38 1024/5052 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-04 14:31:44" 561409.05 4797770.28 NULL C:\SpiceRack\20200904\positions_log.csv 561279.669 4797705.655 ROV ROV_20200915_03 00:14 No take off OK Easter Egg flashing but not released. Handle twisted. Foot twisted. -20
76 30024 1024 5092 179 1 562095.44 4798280.73 0.00 2020-Sep-03 18:41:54.000 0 0 0 1024/5092 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:25:41" 562183.21 4798336.82 PMU-A C:\SpiceRack\20200904\positions_log.csv 562099.891 4798280.664 Surface Surface fail 20200912 14-Sep 10
77 30011 1024 5040 194 1 561030.54 4797535.09 0.00 2020-Sep-03 18:41:54.000 561032.79 4797532.38 38 1024/5040 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 13:57:44" 561115.93 4797588.62 PMU-A C:\SpiceRack\20200904\positions_log.csv 561031.33 4797533.846 Fishermen Fishermen OK 20200912 Fishing boat 13-Sep -20
78 30012 1024 5044 199 1 561112.45 4797592.44 0.00 2020-Sep-03 18:41:54.000 561116.39 4797589.21 38 1024/5044 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 18:15:00" 561081.53 4797635.15 SLEEP C:\SpiceRack\20200904\positions_log.csv 561114.812 4797592.912 In Gabia_3 In Gabia_3 OK 20200912 20
79 30013 1024 5048 204 1 561194.37 4797649.8 0.00 2020-Sep-03 18:41:54.000 561081.53 4797635.15 -10 1024/5048 0.0 255.10 0 0 0 0.0 0 3-Sep-20 247 6:41:54 247184154 "2020-09-03 14:00:24" 561279.81 4797702.23 PMU-A C:\SpiceRack\20200904\positions_log.csv 561085.145 4797636.548 ROV ROV_20200914_08 0:19 No take off OK Front truster broken, left rear truster broken -20
80 40025 1036 5096 1 1 562349.42 4798092.35 0.00 2020-Sep-03 18:41:55.000 562345.25 4798095.82 39 1036/5096 0.0 320.30 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:38:41" 560558.73 4796466.74 LANDED C:\SpiceRack\20200904\positions_log.csv 562348.147 4798090.22 In Gabia_4 In Gabia_4 OK 20200912 20
81 40020 1036 5076 29 1 561939.85 4797805.56 0.00 2020-Sep-03 18:41:55.000 0 0 0 1036/5076 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:39:39" 562015.28 4797864.19 PMU-A C:\SpiceRack\20200904\positions_log.csv 561938.12 4797804.082 No wake Up No wake Up -20
82 40008 1036 5028 58 1 560956.87 4797117.27 0.00 2020-Sep-03 18:41:55.000 560952.47 4797119.6 39 1036/5028 0.0 331.70 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:11:52" 561033.05 4797174.56 PMU-A C:\SpiceRack\20200904\positions_log.csv 560955.049 4797117.106 In Gabia_4 In Gabia_4 Fail 20200912 20
83 40002 1036 5004 59 1 560465.37 4796773.12 0.00 2020-Sep-03 18:41:55.000 0 0 0 1036/5004 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:26:16" 560542.13 4796830.1 PMU-A C:\SpiceRack\20200904\positions_log.csv 560516.925 4796829.213 No com No com -10
84 40007 1036 5024 60 1 560874.95 4797059.91 0.00 2020-Sep-03 18:41:55.000 560869.67 4797062.62 39 1036/5024 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:13:02" 560952.47 4797119.6 LANDED C:\SpiceRack\20200904\positions_log.csv 560870.264 4797060.508 In Gabia_4 In Gabia_4 OK 20200912 Eastern Egg and front truster HS 13-Sep 20
85 40023 1036 5088 61 1 562185.59 4797977.63 0.00 2020-Sep-03 18:41:55.000 562184.43 4797977.33 40 1036/5088 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:33:57" 562264.35 4798037.42 LANDING C:\SpiceRack\20200904\positions_log.csv 562183.736 4797976.341 Surface Surface OK 20200912 10
86 40021 1036 5080 68 1 562021.76 4797862.92 0.00 2020-Sep-03 18:41:55.000 562017.15 4797864.24 38 1036/5080 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:38:13" 562097.3 4797921.75 PMU-A C:\SpiceRack\20200904\positions_log.csv 562018.724 4797862.732 In Gabia_4 In Gabia_4 OK 20200912 20
87 40004 1036 5012 79 1 560629.2 4796887.84 0.00 2020-Sep-03 18:41:55.000 560624.31 4796890.41 39 1036/5012 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:23:23" 560705.23 4796944.96 PMU-A C:\SpiceRack\20200904\positions_log.csv 560626.537 4796887.91 In Gabia_4 In Gabia_4 OK 20200912 14-Sep 20
88 40003 1036 5008 92 1 560547.29 4796830.48 0.00 2020-Sep-03 18:41:55.000 560541.67 4796829.97 39 1036/5008 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:24:43" 560624.31 4796890.41 LANDING C:\SpiceRack\20200904\positions_log.csv 560543.26 4796827.954 In Gabia_4 In Gabia_4 OK 20200912 20
89 40022 1036 5084 95 1 562103.68 4797920.27 0.00 2020-Sep-03 18:41:55.000 562097.86 4797921.15 38 1036/5084 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:35:58" 562184.48 4797977.26 PMU-A C:\SpiceRack\20200904\positions_log.csv 562100.521 4797918.079 In Gabia_4 In Gabia_4 OK 20200912 20
90 40006 1036 5020 98 1 560793.03 4797002.55 0.00 2020-Sep-03 18:41:55.000 560788.07 4797001.68 39 1036/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:15:20" 560869.67 4797062.62 LANDING C:\SpiceRack\20200904\positions_log.csv 560789.596 4797001.951 No take off No take off -20
91 40005 1036 5016 107 1 560711.12 4796945.19 0.00 2020-Sep-03 18:41:55.000 560706.95 4796941.36 35 1036/5016 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:16:33" 560788.07 4797001.68 LANDING C:\SpiceRack\20200904\positions_log.csv 560707.482 4796943.492 Surface Surface OK 20200913 10
92 40024 1036 5092 125 1 562267.51 4798034.99 0.00 2020-Sep-03 18:41:55.000 562264.35 4798037.42 39 1036/5092 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:32:01" 562345.25 4798095.82 LANDED C:\SpiceRack\20200904\positions_log.csv 562268.344 4798035.57 No com No com -10
93 40019 1036 5072 159 1 561857.93 4797748.2 0.00 2020-Sep-03 18:41:55.000 561854.63 4797752.19 39 1036/5072 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:45:09" 561937.53 4797807.32 PMU-A C:\SpiceRack\20200904\positions_log.csv 561856.892 4797750.094 In Gabia_4 In Gabia_4 OK 20200913 15-Sep 20
94 40018 1036 5068 165 1 561776.02 4797690.84 0.00 2020-Sep-03 18:41:55.000 561772.28 4797692.42 39 1036/5068 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:47:07" 561854.63 4797752.19 LANDING C:\SpiceRack\20200904\positions_log.csv 561774.503 4797689.653 Fished EE_20200915_01 No com Water inside - Not operative 15-Sep -10
95 40017 1036 5064 166 1 561694.1 4797633.49 0.00 2020-Sep-03 18:41:55.000 561688.08 4797630.81 35 1036/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:49:02" 561772.28 4797692.42 LANDING C:\SpiceRack\20200904\positions_log.csv 561690.857 4797632.607 Fished EE_20200915_02 No take off -20
96 40012 1036 5044 169 1 561284.53 4797346.7 0.00 2020-Sep-03 18:41:55.000 561280.16 4797349.01 39 1036/5044 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:01:50" 561362.94 4797406.42 PMU-A C:\SpiceRack\20200904\positions_log.csv 561281.64 4797345.104 In Gabia_4 In Gabia_4 OK 20200912 Eastern Egg HS 13-Sep 20
97 40015 1036 5056 170 1 561530.27 4797518.77 0.00 2020-Sep-03 18:41:55.000 561524.03 4797522.83 39 1036/5056 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 561527.75 4797521.011 In Gabia_4 In Gabia_4 OK 20200912 15-Sep 20
98 40011 1036 5040 180 1 561202.61 4797289.34 0.00 2020-Sep-03 18:41:55.000 561197.96 4797282.17 35 1036/5040 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:03:10" 561280.16 4797349.01 LANDING C:\SpiceRack\20200904\positions_log.csv 561198.278 4797284.676 No take off No take off -20
99 40010 1036 5036 181 1 561120.7 4797231.98 0.00 2020-Sep-03 18:41:55.000 561114.59 4797231.45 39 1036/5036 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:04:52" 561197.96 4797282.17 LANDING C:\SpiceRack\20200904\positions_log.csv 561117.732 4797229.509 In Gabia_4 In Gabia_4 OK 20200912 20
100 40009 1036 5032 182 1 561038.78 4797174.62 0.00 2020-Sep-03 18:41:55.000 561033.13 4797174.35 39 1036/5032 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:10:09" 561114.59 4797231.45 LANDING C:\SpiceRack\20200904\positions_log.csv 561034.728 4797173.322 Fished EE_20200915_03 No wake Up Water inside - Not operative -20
101 40014 1036 5052 196 1 561448.36 4797461.41 0.00 2020-Sep-03 18:41:55.000 561446.02 4797465.12 39 1036/5052 0.0 334.50 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:58:41" 561524.03 4797522.83 LANDING C:\SpiceRack\20200904\positions_log.csv 561447.941 4797461.812 In Gabia_4 In Gabia_4 OK 20200913 Front truster HS 13-Sep 20
102 40001 1036 5000 197 1 560383.46 4796715.76 0.00 2020-Sep-03 18:41:55.000 560381.92 4796717.07 39 1036/5000 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 560382.385 4796716.119 In Gabia_4 In Gabia_4 OK 20200913 20
103 40013 1036 5048 203 1 561366.44 4797404.05 0.00 2020-Sep-03 18:41:55.000 561362.93 4797405.85 39 1036/5048 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:00:08" 561446.02 4797465.12 LANDED C:\SpiceRack\20200904\positions_log.csv 561365.638 4797405.012 In Gabia_4 In Gabia_4 OK 20200913 14-Sep 20
104 40016 1036 5060 208 1 561612.19 4797576.13 0.00 2020-Sep-04 10:34:00.000 561617.1 4797575.4 0 1036/5060 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 14:50:22" 561689.35 4797633.22 PMU-A C:\SpiceRack\20200904\positions_log.csv 561616.807 4797573.081 ROV ROV_20200915_06 00:49 Manta_12443 -30
105 50023 1048 5088 16 1 562357.67 4797731.89 0.00 2020-Sep-03 18:41:56.000 562364.06 4797731.22 40 1048/5088 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:27:58" 562444.78 4797789.45 LANDING C:\SpiceRack\20200904\positions_log.csv 562365.813 4797735.62 No com No com -10
106 50024 1048 5092 18 1 562439.58 4797789.24 0.00 2020-Sep-03 18:41:56.000 562444.78 4797789.45 40 1048/5092 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:29:40" 562525.03 4797844.5 LANDED C:\SpiceRack\20200904\positions_log.csv 562445.395 4797793.56 Gabia missed Gabia missed 10
107 50025 1048 5096 24 1 562521.5 4797846.6 0.00 2020-Sep-03 18:41:56.000 562525.03 4797844.5 40 1048/5096 0.0 141.70 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:43:45" 560721.14 4796224.79 PMU-A C:\SpiceRack\20200904\positions_log.csv 562523.401 4797845.446 In Gabia_4 In Gabia_4 OK 20200912 14-Sep 20
108 50022 1048 5084 35 1 562275.75 4797674.53 0.00 2020-Sep-03 18:41:56.000 562279.55 4797672.75 39 1048/5084 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:29:02" 562447.28 4797840.15 NULL C:\SpiceRack\20200904\positions_log.csv 562279.206 4797674.313 Surface Surface OK 20200912 10
109 50021 1048 5080 36 1 562193.84 4797617.17 0.00 2020-Sep-03 18:41:56.000 0 0 0 1048/5080 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:25:09" 562278.63 4797671.28 PMU-A C:\SpiceRack\20200904\positions_log.csv 562133.088 4797594.781 Fished EE_20200912_02 Fished OK 20200913 Eastern Egg HS 13-Sep -10
110 50005 1048 5016 47 1 560883.19 4796699.45 0.00 2020-Sep-04 09:56:00.000 560911.4 4796699.2 0 1048/5016 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:52:27" 561192.97 4796932.48 NAVIG C:\SpiceRack\20200904\positions_log.csv 560883.164 4796698.138 ROV ROV_20200915_07 00:27 Manta_1240 -30
111 50010 1048 5036 51 1 561292.77 4796986.24 0.00 2020-Sep-03 18:41:56.000 0 0 0 1048/5036 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:02:00" 561378.14 4797042.43 PMU-A C:\SpiceRack\20200904\positions_log.csv 561443.887 4797094.158 No com No com -10
112 50004 1048 5012 53 1 560801.28 4796642.09 0.00 2020-Sep-03 18:41:55.000 560805.24 4796641.73 38 1048/5012 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-04 13:57:40" 560880.65 4796701.28 NULL C:\SpiceRack\20200904\positions_log.csv 560802.513 4796643.087 In Gabia_4 In Gabia_4 OK 20200912 15-Sep 20
113 50011 1048 5040 108 1 561374.68 4797043.59 0.00 2020-Sep-03 18:41:56.000 561378.55 4797043.79 38 1048/5040 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:03:26" 561459.17 4797098 LANDING C:\SpiceRack\20200904\positions_log.csv 561378.269 4797046.361 Surface Surface OK 20200914 14-Sep 10
114 50003 1048 5008 113 1 560719.36 4796584.73 0.00 2020-Sep-03 18:41:55.000 560724.85 4796582.47 40 1048/5008 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:42:32" 560805.24 4796641.73 LANDING C:\SpiceRack\20200904\positions_log.csv 560725.489 4796585.824 In Gabia_4 In Gabia_4 OK 20200912 15-Sep 20
115 50012 1048 5044 114 1 561456.6 4797100.95 0.00 2020-Sep-03 18:41:56.000 561459.17 4797098 40 1048/5044 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:02:45" 561444.84 4797094.82 NULL C:\SpiceRack\20200904\positions_log.csv 561460.668 4797101.438 Fishermen Fishermen OK 20200912 Front truster HS 13-Sep -20
116 50008 1048 5028 116 1 561128.94 4796871.52 0.00 2020-Sep-03 18:41:55.000 561130.76 4796871.98 39 1048/5028 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 561129.58 4796872.38 In Gabia_4 In Gabia_4 OK 20200914 20
117 50006 1048 5020 127 1 560965.11 4796756.81 0.00 2020-Sep-03 18:41:55.000 560966.27 4796753.71 38 1048/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:49:18" 561048.61 4796808.77 LANDING C:\SpiceRack\20200904\positions_log.csv 560964.118 4796754.465 In Gabia_4 In Gabia_4 OK 20200912 20
118 50007 1048 5024 133 1 561047.02 4796814.16 0.00 2020-Sep-03 18:41:55.000 561048.61 4796808.77 40 1048/5024 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:51:16" 561130.94 4796871.76 PMU-A C:\SpiceRack\20200904\positions_log.csv 561049.771 4796813.181 In Gabia_4 In Gabia_4 OK 20200913 20
119 50013 1048 5048 142 1 561538.51 4797158.31 0.00 2020-Sep-03 18:41:56.000 0 0 0 1048/5048 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:06:25" 561633.31 4797220.15 LANDED C:\SpiceRack\20200904\positions_log.csv No com No com -10
120 50017 1048 5064 148 1 561866.17 4797387.74 0.00 2020-Sep-03 18:41:56.000 561871.62 4797384.94 40 1048/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:15:07" 561948.3 4797442.48 PMU-A C:\SpiceRack\20200904\positions_log.csv 561870.272 4797388.384 Surface Surface OK 20200912 10
121 50018 1048 5068 151 1 561948.09 4797445.1 0.00 2020-Sep-03 18:41:56.000 561948.87 4797442 40 1048/5068 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:18:00" 562019.18 4797502.75 PMU-A C:\SpiceRack\20200904\positions_log.csv 561951.805 4797447.679 No take off No take off -20
122 50015 1048 5056 171 1 561702.34 4797273.02 0.00 2020-Sep-03 18:41:56.000 561707.62 4797273.05 38 1048/5056 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:12:40" 561786.59 4797327.81 PMU-A C:\SpiceRack\20200904\positions_log.csv 561706.482 4797274.799 Surface Surface OK 20200912 15-Sep 10
123 50001 1048 5000 183 1 560555.53 4796470.02 0.00 2020-Sep-03 18:41:55.000 560558.73 4796466.74 40 1048/5000 0.0 150.20 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:40:08" 560644.57 4796524.63 PMU-A C:\SpiceRack\20200904\positions_log.csv 560558.708 4796469.609 In Gabia_4 In Gabia_4 OK 20200912 14-Sep 20
124 50019 1048 5072 191 1 562030.01 4797502.46 0.00 2020-Sep-03 18:41:56.000 562018.97 4797502.9 39 1048/5072 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:17:44" 562132.21 4797564.9 LANDING C:\SpiceRack\20200904\positions_log.csv 562021.638 4797502.011 Surface Surface OK 20200913 10
125 50002 1048 5004 195 1 560637.45 4796527.38 0.00 2020-Sep-03 18:41:55.000 560644.22 4796524.88 40 1048/5004 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:55 247184155 "2020-09-03 15:41:15" 560724.53 4796582.18 PMU-A C:\SpiceRack\20200904\positions_log.csv 560643.425 4796528.087 In Gabia_4 In Gabia_4 OK 20200913 15-Sep 20
126 50014 1048 5052 200 1 561620.43 4797215.67 0.00 2020-Sep-03 18:41:56.000 561633.31 4797220.15 40 1048/5052 0.0 141.70 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:07:29" 561707.38 4797272.18 PMU-A C:\SpiceRack\20200904\positions_log.csv 561633.625 4797219.467 No wake Up No wake Up -20
127 50016 1048 5060 201 1 561784.26 4797330.38 0.00 2020-Sep-03 18:41:56.000 561787.72 4797329.12 39 1048/5060 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 16:13:44" 561871.62 4797384.94 LANDING C:\SpiceRack\20200904\positions_log.csv 561788.421 4797330.933 No wake Up No wake Up -20
128 50020 1048 5076 206 1 562111.92 4797559.81 0.00 2020-Sep-03 18:41:56.000 562132.21 4797564.9 40 1048/5076 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 562131.838 4797568.01 Surface Surface OK 20200914 14-Sep 10
129 50009 1048 5032 210 1 561210.85 4796928.88 0.00 2020-Sep-03 18:41:56.000 0 0 0 1048/5032 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 561196.164 4796930.859 No com No com -10
130 60017 1060 5064 5 1 562038.25 4797141.99 0.00 2020-Sep-03 18:41:57.000 562032.52 4797142.46 41 1060/5064 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 17:03:58" 562114.01 4797201.91 PMU-A C:\SpiceRack\20200904\positions_log.csv 562034.626 4797139.477 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
131 60016 1060 5060 6 1 561956.33 4797084.64 0.00 2020-Sep-03 18:41:57.000 561954.44 4797089.04 41 1060/5060 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 17:05:09" 562032.39 4797142.81 PMU-A C:\SpiceRack\20200904\positions_log.csv 561954.287 4797085.005 Surface failed Surface failed -10
132 60015 1060 5056 17 1 561874.42 4797027.28 0.00 2020-Sep-03 18:41:57.000 561870.29 4797030.43 41 1060/5056 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 17:06:25" 561954.44 4797089.04 LANDING C:\SpiceRack\20200904\positions_log.csv 561871.367 4797029.29 in Gabia_5 in Gabia_5 OK 20200914 15-Sep 20
133 60007 1060 5024 23 1 561219.1 4796568.42 0.00 2020-Sep-03 18:41:56.000 561211.31 4796571.23 41 1060/5024 0.0 343.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:31:13" 561295.77 4796627.79 PMU-A C:\SpiceRack\20200904\positions_log.csv 561215.18 4796566.522 Surface Surface OK 20200913 10
134 60020 1060 5076 25 1 562283.99 4797314.07 0.00 2020-Sep-03 18:41:57.000 562278.7 4797316.4 39 1060/5076 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562280.32 4797312.616 in Gabia_5 in Gabia_5 OK 20200913 20
135 60019 1060 5072 26 1 562202.08 4797256.71 0.00 2020-Sep-03 18:41:57.000 0 0 0 1060/5072 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 17:00:27" 562277.25 4797318.15 PMU-A C:\SpiceRack\20200904\positions_log.csv 562171.926 4797287.809 Surface Surface OK 20200913 Eastern Egg HS 13-Sep 10
136 60022 1060 5084 27 1 562447.82 4797428.78 0.00 2020-Sep-03 18:41:57.000 562441.18 4797433.37 41 1060/5084 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 16:40:59" 562594.05 4797574.21 NULL C:\SpiceRack\20200904\positions_log.csv 562444.789 4797429.978 in Gabia_5 in Gabia_5 OK 20200913 Front truster HS 13-Sep 20
137 60023 1060 5088 30 1 562529.74 4797486.14 0.00 2020-Sep-03 18:41:57.000 0 0 0 1060/5088 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 16:37:47" 562605.01 4797544.81 LANDING C:\SpiceRack\20200904\positions_log.csv 562594.718 4797574.544 No take off No take off -20
138 60024 1060 5092 31 1 562611.65 4797543.5 0.00 2020-Sep-03 18:41:57.000 562605.01 4797544.81 41 1060/5092 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 16:38:06" 562715.12 4797714.73 NULL C:\SpiceRack\20200904\positions_log.csv 562609.086 4797540.06 in Gabia_5 in Gabia_5 OK 20200914 20
139 60025 1060 5096 32 1 562693.57 4797600.86 0.00 2020-Sep-03 18:41:57.000 0 0 0 1060/5096 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562714.895 4797711.251 No com No com -10
140 60018 1060 5068 39 1 562120.16 4797199.35 0.00 2020-Sep-03 18:41:57.000 562114.43 4797200.67 41 1060/5068 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 562115.953 4797198.078 in Gabia_5 in Gabia_5 OK 20200914 20
141 60010 1060 5036 41 1 561464.84 4796740.49 0.00 2020-Sep-03 18:41:56.000 561461.77 4796737.26 40 1060/5036 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:20:53" 561540.86 4796800.29 LANDED C:\SpiceRack\20200904\positions_log.csv 561462.714 4796738.599 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
142 60004 1060 5012 50 1 560973.35 4796396.34 0.00 2020-Sep-03 18:41:56.000 560967.85 4796398.88 41 1060/5012 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:38:41" 561051.42 4796457.21 PMU-A C:\SpiceRack\20200904\positions_log.csv 560971 4796394.574 in Gabia_5 in Gabia_5 OK 20200913 20
143 60014 1060 5052 65 1 561792.5 4796969.92 0.00 2020-Sep-03 18:41:57.000 0 0 0 1060/5052 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 17:14:51" 561870.46 4797030.51 PMU-A C:\SpiceRack\20200904\positions_log.csv 561838.329 4797028.333 No com No com -10
144 60013 1060 5048 66 1 561710.59 4796912.56 0.00 2020-Sep-03 18:41:56.000 561707.37 4796915.04 38 1060/5048 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-04 14:33:09" -1000 -1000 NULL C:\SpiceRack\20200904\positions_log.csv 561707.996 4796913.517 in Gabia_5 in Gabia_5 OK 20200914 15-Sep 20
145 60021 1060 5080 80 1 562365.91 4797371.42 0.00 2020-Sep-04 10:54:00.000 562368.3 4797368.8 0 1060/5080 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 "2020-09-03 16:40:40" 562441.18 4797433.37 LANDING C:\SpiceRack\20200904\positions_log.csv 562368.733 4797370.963 Manta_12614 Manta_12614 Manta_12614 -30
146 60002 1060 5004 81 1 560809.52 4796281.63 0.00 2020-Sep-03 18:41:56.000 560806.44 4796286.11 41 1060/5004 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:41:15" 560888.53 4796340.44 LANDED C:\SpiceRack\20200904\positions_log.csv 560807.441 4796280.46 No take off No take off -20
147 60006 1060 5020 85 1 561137.18 4796511.06 0.00 2020-Sep-03 18:41:56.000 561132.91 4796511.38 41 1060/5020 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:32:40" 561211.31 4796571.23 LANDED C:\SpiceRack\20200904\positions_log.csv 561133.24 4796507.219 in Gabia_5 in Gabia_5 OK 20200913 20
148 60009 1060 5032 101 1 561382.93 4796683.13 0.00 2020-Sep-03 18:41:56.000 561380.32 4796687.55 41 1060/5032 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:28:27" 561462.29 4796737.23 PMU-A C:\SpiceRack\20200904\positions_log.csv 561381.595 4796683.86 No wake Up No wake Up -20
149 60005 1060 5016 121 1 561055.27 4796453.7 0.00 2020-Sep-03 18:41:56.000 561052.07 4796456.34 40 1060/5016 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:33:53" 561132.91 4796511.38 LANDING C:\SpiceRack\20200904\positions_log.csv 561054.43 4796453.122 in Gabia_5 in Gabia_5 OK 20200914 20
150 60001 1060 5000 123 1 560727.6 4796224.27 0.00 2020-Sep-03 18:41:56.000 560722.61 4796222.71 38 1060/5000 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:42:29" 560806.31 4796286.35 PMU-A C:\SpiceRack\20200904\positions_log.csv 560724.094 4796221.694 in Gabia_5 in Gabia_5 OK 20200914 14-Sep 20
151 60008 1060 5028 145 1 561301.01 4796625.78 0.00 2020-Sep-03 18:41:56.000 561295.8 4796627.66 41 1060/5028 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:29:50" 561380.32 4796687.55 LANDING C:\SpiceRack\20200904\positions_log.csv 561298.63 4796625.635 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
152 60012 1060 5044 176 1 561628.67 4796855.21 0.00 2020-Sep-03 18:41:56.000 561624.82 4796856.33 40 1060/5044 0.0 -10.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:18:09" 561705.89 4796919.07 PMU-A C:\SpiceRack\20200904\positions_log.csv 561625.079 4796852.846 Fishermen Fishermen OK 20200912 -20
153 60003 1060 5008 178 1 560891.44 4796338.99 0.00 2020-Sep-03 18:41:56.000 560888.53 4796340.44 40 1060/5008 0.0 328.80 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:39:55" 560967.85 4796398.88 LANDING C:\SpiceRack\20200904\positions_log.csv 560889.279 4796336.938 Surface failed Surface failed -10
154 60011 1060 5040 190 1 561546.76 4796797.85 0.00 2020-Sep-03 18:41:56.000 561540.86 4796800.29 41 1060/5040 0.0 326.00 0 0 0 0.0 0 3-Sep-20 247 6:41:56 247184156 "2020-09-03 17:19:17" 561624.82 4796856.33 LANDING C:\SpiceRack\20200904\positions_log.csv 561542.942 4796795.61 in Gabia_5 in Gabia_5 OK 20200913 Front truster HS 13-Sep 20
155 70019 1072 5072 8 1 562374.15 4797010.96 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5072 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562377.226 4797012.3 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
156 70020 1072 5076 14 1 562456.07 4797068.32 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5076 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562462.045 4797062.995 Gabia missed Surface 10
157 70001 1072 5000 75 1 560899.68 4795978.53 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5000 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 560902.441 4795977.074 in Gabia_5 in Gabia_5 OK 20200914 14-Sep 20
158 70002 1072 5004 96 1 560981.59 4796035.88 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5004 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 560985.309 4796034.419 Surface Surface OK 20200914 10
159 70021 1072 5080 117 1 562537.98 4797125.68 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5080 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562534.141 4797123.61 in Gabia_5 in Gabia_5 OK 20200913 20
160 70025 1072 5096 126 1 562865.64 4797355.11 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5096 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562868.786 4797352.891 in Gabia_5 in Gabia_5 OK 20200913 14-Sep 20
161 70022 1072 5084 130 1 562619.9 4797183.04 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5084 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562626.12 4797180.707 Surface Surface OK 20200914 10
162 70024 1072 5092 135 1 562783.73 4797297.75 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5092 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562789.217 4797297.665 Surface Surface OK 20200913 Eastern Egg and front truster HS 13-Sep 10
163 70018 1072 5068 137 1 562292.24 4796953.61 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5068 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562305.188 4796953.401 Fished EE_20200912_01 Fished OK 20200913 Eastern Egg and front truster HS 13-Sep -10
164 70023 1072 5088 140 1 562701.81 4797240.39 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5088 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562711.023 4797232.686 Surface Surface OK 20200913 14-Sep 10
165 70010 1072 5036 144 1 561636.91 4796494.75 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5036 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561631.634 4796506.086 in Gabia_5 in Gabia_5 OK 20200913 20
166 70007 1072 5024 150 1 561391.17 4796322.67 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5024 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561395.994 4796324.225 in Gabia_5 in Gabia_5 OK 20200914 20
167 70011 1072 5040 158 1 561718.83 4796552.1 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5040 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561587.348 4796631.849 Surface failed Surface failed -10
168 70016 1072 5060 160 1 562128.41 4796838.89 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5060 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562133.38 4796838.645 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
169 70017 1072 5064 161 1 562210.32 4796896.25 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5064 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562123.354 4796853.919 No take off No take off -20
170 70013 1072 5048 162 1 561882.66 4796666.82 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5048 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561802.101 4796622.036 Fishermen Fishermen OK 20200912 Fishing boat 13-Sep -20
171 70015 1072 5056 167 1 562046.49 4796781.53 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5056 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562063.163 4796784.615 in Gabia_5 in Gabia_5 OK 20200913 20
172 70014 1072 5052 168 1 561964.58 4796724.18 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5052 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561958.186 4796728.041 Surface Surface OK 20200913 15-Sep 10
173 70008 1072 5028 173 1 561473.08 4796380.03 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5028 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561473.532 4796379.241 in Gabia_5 in Gabia_5 OK 20200913 20
174 70006 1072 5020 186 1 561309.25 4796265.31 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5020 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561308.411 4796262.927 Surface Surface OK 20200913 14-Sep 10
175 70009 1072 5032 187 1 561555 4796437.39 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5032 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561557.082 4796438.134 in Gabia_5 in Gabia_5 OK 20200913 20
176 70012 1072 5044 189 1 561800.74 4796609.46 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5044 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561803.92 4796606.764 in Gabia_5 in Gabia_5 OK 20200913 15-Sep 20
177 70003 1072 5008 193 1 561063.51 4796093.24 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5008 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 560998.126 4796047.935 in Gabia_5 in Gabia_5 OK 20200913 14-Sep 20
178 70004 1072 5012 202 1 561145.42 4796150.6 0.00 2020-Sep-03 18:41:57.000 0 0 0 1072/5012 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561145.75 4796151.973 No com No com -10
179 70005 1072 5016 205 1 561227.34 4796207.96 0.00 2020-Sep-04 09:28:00.000 561231.5 479622 0 1072/5016 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561225.176 4796206.454 ROV ROV_20200915_08 00:13 Manta_4071 Battery HS -30
180 80008 1084 5028 3 1 561645.16 4796134.28 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5028 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561638.939 4796134.916 In Gabia_6 In Gabia_6 OK 20200914 14-Sep 20
181 80009 1084 5032 9 1 561727.07 4796191.64 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5032 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561725.473 4796192.107 In Gabia_6 In Gabia_6 OK 20200913 Eastern Egg HS 13-Sep 20
182 80005 1084 5016 20 1 561399.41 4795962.21 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5016 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561452.337 4796018.81 No wake Up No wake Up -20
183 80012 1084 5044 22 1 561972.82 4796363.71 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5044 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561966.857 4796366.027 Surface Surface OK 20200914 14-Sep 10
184 80014 1084 5052 33 1 562136.65 4796478.43 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5052 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562135.671 4796478.783 Fishermen Fishermen OK 20200912 -20
185 80015 1084 5056 38 1 562218.56 4796535.79 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5056 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562208.39 4796531.419 Fishermen Fishermen OK 20200912 Fishing boat 13-Sep -20
186 80013 1084 5048 42 1 562054.73 4796421.07 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5048 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562051.96 4796423.01 Surface Surface OK 20200914 14-Sep 10
187 80001 1084 5000 48 1 561071.75 4795732.78 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5000 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561070.56 4795734.655 In Gabia_6 In Gabia_6 OK 20200913 14-Sep 20
188 80016 1084 5060 49 1 562300.48 4796593.15 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5060 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562365.641 4796656.326 Fishermen Fishermen OK 20200913 Fishing boat 13-Sep -20
189 80024 1084 5092 64 1 562955.8 4797052.01 0.00 2020-Sep-03 18:41:58.000 0 0 0 1084/5092 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:58 247184158 562954.669 4797050.97 Fishermen Fishermen OK 20200912 15-Sep -20
190 80019 1084 5072 69 1 562546.22 4796765.22 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5072 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562544.851 4796766.816 Surface failed Surface failed -10
191 80020 1084 5076 70 1 562628.14 4796822.58 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5076 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562626.102 4796821.835 Surface Surface OK 20200914 14-Sep 10
192 80007 1084 5024 74 1 561563.24 4796076.93 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5024 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561623.984 4796133.78 No com No com -10
193 80021 1084 5080 76 1 562710.05 4796879.93 0.00 2020-Sep-03 18:41:58.000 0 0 0 1084/5080 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:58 247184158 562708.586 4796880.264 No com No com -10
194 80022 1084 5084 77 1 562791.97 4796937.29 0.00 2020-Sep-03 18:41:58.000 0 0 0 1084/5084 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:58 247184158 562788.733 4796937.844 No take off No take off -20
195 80023 1084 5088 82 1 562873.89 4796994.65 0.00 2020-Sep-03 18:41:58.000 0 0 0 1084/5088 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:58 247184158 562868.382 4796995.728 No take off No take off -20
196 80006 1084 5020 97 1 561481.33 4796019.57 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5020 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561479.344 4796019.992 In Gabia_6 In Gabia_6 OK 20200914 20
197 80018 1084 5068 105 1 562464.31 4796707.86 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5068 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562460.123 4796712.156 In Gabia_6 In Gabia_6 OK 20200913 Front truster HS 13-Sep 20
198 80010 1084 5036 106 1 561808.99 4796249 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5036 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561807.981 4796250.177 No take off No take off -20
199 80003 1084 5008 111 1 561235.58 4795847.5 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5008 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561234.892 4795851.971 No take off No take off -20
200 80017 1084 5064 112 1 562382.39 4796650.5 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5064 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 562381.774 4796653.825 In Gabia_6 In Gabia_6 OK 20200914 20
201 80004 1084 5012 118 1 561317.5 4795904.85 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5012 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561319.96 4795905.44 No wake Up No wake Up -20
202 80011 1084 5040 120 1 561890.9 4796306.36 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5040 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561887.928 4796309.881 Fishermen Fishermen OK 20200912 -20
203 80025 1084 5096 122 1 563037.72 4797109.36 0.00 2020-Sep-03 18:41:58.000 0 0 0 1084/5096 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:58 247184158 563034.171 4797110.504 In Gabia_6 In Gabia_6 OK 20200913 Left rear truster HS 13-Sep 20
204 80002 1084 5004 163 1 561153.67 4795790.14 0.00 2020-Sep-03 18:41:57.000 0 0 0 1084/5004 0.0 0.00 0 0 0 0.0 0 3-Sep-20 247 6:41:57 247184157 561147.742 4795788.78 No take off No take off -20
205 99999 9999 9999 110 9 9999/9999 Never deployed Never deployed and mere porteuse 14-Sep #N/A
206 99999 9999 9999 62 9 9999/9999 Never deployed Never deployed 13-Sep #N/A

2426
static/darf_nodes.json Normal file

File diff suppressed because it is too large Load Diff

580
static/index.html Normal file
View File

@@ -0,0 +1,580 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seismic H5 Viewer</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0a1628 0%, #1a2744 100%);
color: #e0e0e0;
min-height: 100vh;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-bottom: 20px;
}
h1 {
font-size: 1.8rem;
background: linear-gradient(90deg, #00d9ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 10px;
}
.status {
display: flex;
gap: 20px;
font-size: 0.9rem;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(255,255,255,0.05);
border-radius: 20px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #00ff88;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.status-dot.error { background: #ff4757; animation: none; }
.grid {
display: grid;
grid-template-columns: 350px 1fr;
gap: 20px;
height: calc(100vh - 140px);
}
.panel {
background: rgba(255,255,255,0.03);
border-radius: 16px;
padding: 20px;
border: 1px solid rgba(255,255,255,0.08);
overflow: hidden;
display: flex;
flex-direction: column;
}
.panel h2 {
font-size: 0.9rem;
margin-bottom: 15px;
color: #00d9ff;
text-transform: uppercase;
letter-spacing: 1px;
}
.file-list {
flex: 1;
overflow-y: auto;
}
.file-item {
padding: 14px;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 8px;
border: 1px solid transparent;
background: rgba(0,0,0,0.2);
}
.file-item:hover {
background: rgba(0, 217, 255, 0.1);
border-color: rgba(0, 217, 255, 0.3);
transform: translateX(4px);
}
.file-item.selected {
background: rgba(0, 217, 255, 0.15);
border-color: #00d9ff;
}
.file-name {
font-weight: 600;
font-size: 0.85rem;
color: #fff;
}
.file-meta {
font-size: 0.75rem;
color: #888;
margin-top: 6px;
display: flex;
gap: 12px;
}
.file-meta span {
display: flex;
align-items: center;
gap: 4px;
}
.viewer-panel {
display: flex;
flex-direction: column;
gap: 15px;
}
.chart-container {
flex: 1;
background: rgba(0,0,0,0.3);
border-radius: 12px;
padding: 20px;
min-height: 300px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
}
.channel-btn {
padding: 10px 20px;
border: 1px solid rgba(255,255,255,0.15);
background: rgba(0,0,0,0.3);
color: #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
.channel-btn:hover {
border-color: #00d9ff;
color: #00d9ff;
}
.channel-btn.active {
background: linear-gradient(135deg, #00d9ff, #00a8cc);
color: #0a1628;
border-color: transparent;
}
.data-type-toggle {
margin-left: auto;
display: flex;
gap: 5px;
background: rgba(0,0,0,0.3);
padding: 4px;
border-radius: 8px;
}
.data-type-toggle button {
padding: 8px 16px;
border: none;
background: transparent;
color: #888;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.data-type-toggle button.active {
background: #00d9ff;
color: #0a1628;
}
.stats-bar {
display: flex;
gap: 20px;
padding: 15px 20px;
background: rgba(0,0,0,0.2);
border-radius: 10px;
}
.stat {
text-align: center;
}
.stat-value {
font-size: 1.3rem;
font-weight: 700;
color: #00ff88;
}
.stat-label {
font-size: 0.7rem;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(0, 217, 255, 0.2);
border-top-color: #00d9ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
text-align: center;
}
.empty-state svg {
width: 80px;
height: 80px;
margin-bottom: 20px;
opacity: 0.3;
}
.empty-state h3 {
color: #888;
margin-bottom: 8px;
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(0,217,255,0.3); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(0,217,255,0.5); }
</style>
</head>
<body>
<div class="container">
<header>
<h1>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="url(#grad)" stroke-width="2">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#00d9ff"/>
<stop offset="100%" style="stop-color:#00ff88"/>
</linearGradient>
</defs>
<path d="M2 12h4l3-9 4 18 3-9h6"/>
</svg>
Seismic H5 Viewer
</h1>
<div class="status">
<div class="status-item">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Connecting...</span>
</div>
<div class="status-item" id="fileCount">— files</div>
</div>
</header>
<div class="grid">
<div class="panel">
<h2>📁 H5 Files</h2>
<div class="file-list" id="fileList">
<div class="loading">
<div class="spinner"></div>
Loading files...
</div>
</div>
</div>
<div class="panel viewer-panel" id="viewerPanel">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M2 12h4l3-9 4 18 3-9h6"/>
</svg>
<h3>Select a file</h3>
<p>Choose an H5 file from the list to view seismic waveforms</p>
</div>
</div>
</div>
</div>
<script>
const API_BASE = '/seismic-api/api';
let currentFile = null;
let currentChannel = 'channel_1';
let currentDataType = 'calibrated_data';
let chart = null;
async function fetchAPI(endpoint) {
const res = await fetch(`${API_BASE}${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
async function checkHealth() {
try {
const data = await fetchAPI('/h5/files');
document.getElementById('statusDot').className = 'status-dot';
document.getElementById('statusText').textContent = 'Connected';
document.getElementById('fileCount').textContent = `${data.length} files`;
return data;
} catch (e) {
document.getElementById('statusDot').className = 'status-dot error';
document.getElementById('statusText').textContent = 'Disconnected';
return null;
}
}
async function loadFiles() {
try {
const files = await checkHealth();
const list = document.getElementById('fileList');
if (!files || files.length === 0) {
list.innerHTML = '<div class="empty-state"><p>No H5 files found</p></div>';
return;
}
// Group by node
const byNode = {};
files.forEach(f => {
const node = f.nodeId || 'Unknown';
if (!byNode[node]) byNode[node] = [];
byNode[node].push(f);
});
list.innerHTML = files.map(f => `
<div class="file-item" onclick="selectFile('${f.filename}')" data-file="${f.filename}">
<div class="file-name">Node ${f.nodeId} - ${f.date}</div>
<div class="file-meta">
<span>⏱️ ${formatDuration(f.duration_sec)}</span>
<span>📊 ${f.n_channels} ch</span>
<span>🎵 ${f.sample_rate_hz} Hz</span>
</div>
</div>
`).join('');
} catch (e) {
document.getElementById('fileList').innerHTML =
`<div class="empty-state"><p>Error: ${e.message}</p></div>`;
}
}
async function selectFile(filename) {
currentFile = filename;
document.querySelectorAll('.file-item').forEach(el => {
el.classList.toggle('selected', el.dataset.file === filename);
});
const panel = document.getElementById('viewerPanel');
panel.innerHTML = '<div class="loading"><div class="spinner"></div>Loading waveform...</div>';
try {
const data = await fetchAPI(`/h5/data?file=${filename}`);
const channels = Object.keys(data.data || {});
panel.innerHTML = `
<div class="controls">
${channels.map((ch, i) => `
<button class="channel-btn ${i === 0 ? 'active' : ''}"
onclick="loadChannel('${ch}', this)">
${ch.replace('_', ' ').toUpperCase()}
</button>
`).join('')}
<div class="data-type-toggle">
<button class="active" onclick="setDataType('calibrated', this)">Calibrated</button>
<button onclick="setDataType('raw', this)">Raw</button>
</div>
</div>
<div class="chart-container">
<canvas id="waveformChart"></canvas>
</div>
<div class="stats-bar">
<div class="stat">
<div class="stat-value">${data.metadata?.n_samples?.toLocaleString() || '—'}</div>
<div class="stat-label">Samples</div>
</div>
<div class="stat">
<div class="stat-value">${data.metadata?.sample_rate_hz || '—'}</div>
<div class="stat-label">Sample Rate (Hz)</div>
</div>
<div class="stat">
<div class="stat-value">${formatDuration(data.metadata?.duration_sec)}</div>
<div class="stat-label">Duration</div>
</div>
<div class="stat">
<div class="stat-value" id="statMin">—</div>
<div class="stat-label">Min</div>
</div>
<div class="stat">
<div class="stat-value" id="statMax">—</div>
<div class="stat-label">Max</div>
</div>
</div>
`;
if (channels.length > 0) {
currentChannel = channels[0];
drawWaveform(data.data[currentChannel], data.metadata);
}
} catch (e) {
panel.innerHTML = `<div class="empty-state"><h3>Error</h3><p>${e.message}</p></div>`;
}
}
async function loadChannel(channel, btn) {
currentChannel = channel;
document.querySelectorAll('.channel-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
try {
const data = await fetchAPI(`/h5/data?file=${currentFile}`);
if (data.data[channel]) {
drawWaveform(data.data[channel], data.metadata);
}
} catch (e) {
console.error('Error loading channel:', e);
}
}
function setDataType(type, btn) {
currentDataType = type === 'raw' ? 'raw_data' : 'calibrated_data';
document.querySelectorAll('.data-type-toggle button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Reload current file with new data type
if (currentFile) selectFile(currentFile);
}
function drawWaveform(channelData, metadata) {
const ctx = document.getElementById('waveformChart').getContext('2d');
if (chart) chart.destroy();
// Downsample for display
const maxPoints = 2000;
const step = Math.max(1, Math.floor(channelData.length / maxPoints));
const displayData = [];
const labels = [];
for (let i = 0; i < channelData.length; i += step) {
const slice = channelData.slice(i, Math.min(i + step, channelData.length));
displayData.push({
x: i / (metadata?.sample_rate_hz || 500),
y: slice.reduce((a, b) => a + b, 0) / slice.length
});
}
// Calculate stats
const min = Math.min(...channelData);
const max = Math.max(...channelData);
document.getElementById('statMin').textContent = min.toFixed(2);
document.getElementById('statMax').textContent = max.toFixed(2);
chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: currentChannel,
data: displayData,
borderColor: '#00d9ff',
backgroundColor: 'rgba(0, 217, 255, 0.1)',
fill: true,
pointRadius: 0,
borderWidth: 1.5,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
titleColor: '#00d9ff',
bodyColor: '#fff',
borderColor: '#00d9ff',
borderWidth: 1
}
},
scales: {
x: {
type: 'linear',
title: {
display: true,
text: 'Time (seconds)',
color: '#666'
},
ticks: { color: '#555' },
grid: { color: 'rgba(255,255,255,0.03)' }
},
y: {
title: {
display: true,
text: 'Amplitude',
color: '#666'
},
ticks: { color: '#555' },
grid: { color: 'rgba(255,255,255,0.03)' }
}
}
}
});
}
function formatDuration(sec) {
if (!sec) return '—';
if (sec < 60) return `${sec.toFixed(0)}s`;
if (sec < 3600) return `${Math.floor(sec/60)}m ${Math.floor(sec%60)}s`;
return `${Math.floor(sec/3600)}h ${Math.floor((sec%3600)/60)}m`;
}
// Init
loadFiles();
setInterval(() => checkHealth(), 30000);
</script>
</body>
</html>

580
viewer.html Normal file
View File

@@ -0,0 +1,580 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seismic H5 Viewer</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #0a1628 0%, #1a2744 100%);
color: #e0e0e0;
min-height: 100vh;
}
.container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid rgba(255,255,255,0.1);
margin-bottom: 20px;
}
h1 {
font-size: 1.8rem;
background: linear-gradient(90deg, #00d9ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 10px;
}
.status {
display: flex;
gap: 20px;
font-size: 0.9rem;
}
.status-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(255,255,255,0.05);
border-radius: 20px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #00ff88;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.status-dot.error { background: #ff4757; animation: none; }
.grid {
display: grid;
grid-template-columns: 350px 1fr;
gap: 20px;
height: calc(100vh - 140px);
}
.panel {
background: rgba(255,255,255,0.03);
border-radius: 16px;
padding: 20px;
border: 1px solid rgba(255,255,255,0.08);
overflow: hidden;
display: flex;
flex-direction: column;
}
.panel h2 {
font-size: 0.9rem;
margin-bottom: 15px;
color: #00d9ff;
text-transform: uppercase;
letter-spacing: 1px;
}
.file-list {
flex: 1;
overflow-y: auto;
}
.file-item {
padding: 14px;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 8px;
border: 1px solid transparent;
background: rgba(0,0,0,0.2);
}
.file-item:hover {
background: rgba(0, 217, 255, 0.1);
border-color: rgba(0, 217, 255, 0.3);
transform: translateX(4px);
}
.file-item.selected {
background: rgba(0, 217, 255, 0.15);
border-color: #00d9ff;
}
.file-name {
font-weight: 600;
font-size: 0.85rem;
color: #fff;
}
.file-meta {
font-size: 0.75rem;
color: #888;
margin-top: 6px;
display: flex;
gap: 12px;
}
.file-meta span {
display: flex;
align-items: center;
gap: 4px;
}
.viewer-panel {
display: flex;
flex-direction: column;
gap: 15px;
}
.chart-container {
flex: 1;
background: rgba(0,0,0,0.3);
border-radius: 12px;
padding: 20px;
min-height: 300px;
}
.controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
}
.channel-btn {
padding: 10px 20px;
border: 1px solid rgba(255,255,255,0.15);
background: rgba(0,0,0,0.3);
color: #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
.channel-btn:hover {
border-color: #00d9ff;
color: #00d9ff;
}
.channel-btn.active {
background: linear-gradient(135deg, #00d9ff, #00a8cc);
color: #0a1628;
border-color: transparent;
}
.data-type-toggle {
margin-left: auto;
display: flex;
gap: 5px;
background: rgba(0,0,0,0.3);
padding: 4px;
border-radius: 8px;
}
.data-type-toggle button {
padding: 8px 16px;
border: none;
background: transparent;
color: #888;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.data-type-toggle button.active {
background: #00d9ff;
color: #0a1628;
}
.stats-bar {
display: flex;
gap: 20px;
padding: 15px 20px;
background: rgba(0,0,0,0.2);
border-radius: 10px;
}
.stat {
text-align: center;
}
.stat-value {
font-size: 1.3rem;
font-weight: 700;
color: #00ff88;
}
.stat-label {
font-size: 0.7rem;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(0, 217, 255, 0.2);
border-top-color: #00d9ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
text-align: center;
}
.empty-state svg {
width: 80px;
height: 80px;
margin-bottom: 20px;
opacity: 0.3;
}
.empty-state h3 {
color: #888;
margin-bottom: 8px;
}
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(0,217,255,0.3); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(0,217,255,0.5); }
</style>
</head>
<body>
<div class="container">
<header>
<h1>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="url(#grad)" stroke-width="2">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#00d9ff"/>
<stop offset="100%" style="stop-color:#00ff88"/>
</linearGradient>
</defs>
<path d="M2 12h4l3-9 4 18 3-9h6"/>
</svg>
Seismic H5 Viewer
</h1>
<div class="status">
<div class="status-item">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Connecting...</span>
</div>
<div class="status-item" id="fileCount">— files</div>
</div>
</header>
<div class="grid">
<div class="panel">
<h2>📁 H5 Files</h2>
<div class="file-list" id="fileList">
<div class="loading">
<div class="spinner"></div>
Loading files...
</div>
</div>
</div>
<div class="panel viewer-panel" id="viewerPanel">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M2 12h4l3-9 4 18 3-9h6"/>
</svg>
<h3>Select a file</h3>
<p>Choose an H5 file from the list to view seismic waveforms</p>
</div>
</div>
</div>
</div>
<script>
const API_BASE = '/seismic-api/api';
let currentFile = null;
let currentChannel = 'channel_1';
let currentDataType = 'calibrated_data';
let chart = null;
async function fetchAPI(endpoint) {
const res = await fetch(`${API_BASE}${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
}
async function checkHealth() {
try {
const data = await fetchAPI('/h5/files');
document.getElementById('statusDot').className = 'status-dot';
document.getElementById('statusText').textContent = 'Connected';
document.getElementById('fileCount').textContent = `${data.length} files`;
return data;
} catch (e) {
document.getElementById('statusDot').className = 'status-dot error';
document.getElementById('statusText').textContent = 'Disconnected';
return null;
}
}
async function loadFiles() {
try {
const files = await checkHealth();
const list = document.getElementById('fileList');
if (!files || files.length === 0) {
list.innerHTML = '<div class="empty-state"><p>No H5 files found</p></div>';
return;
}
// Group by node
const byNode = {};
files.forEach(f => {
const node = f.nodeId || 'Unknown';
if (!byNode[node]) byNode[node] = [];
byNode[node].push(f);
});
list.innerHTML = files.map(f => `
<div class="file-item" onclick="selectFile('${f.filename}')" data-file="${f.filename}">
<div class="file-name">Node ${f.nodeId} - ${f.date}</div>
<div class="file-meta">
<span>⏱️ ${formatDuration(f.duration_sec)}</span>
<span>📊 ${f.n_channels} ch</span>
<span>🎵 ${f.sample_rate_hz} Hz</span>
</div>
</div>
`).join('');
} catch (e) {
document.getElementById('fileList').innerHTML =
`<div class="empty-state"><p>Error: ${e.message}</p></div>`;
}
}
async function selectFile(filename) {
currentFile = filename;
document.querySelectorAll('.file-item').forEach(el => {
el.classList.toggle('selected', el.dataset.file === filename);
});
const panel = document.getElementById('viewerPanel');
panel.innerHTML = '<div class="loading"><div class="spinner"></div>Loading waveform...</div>';
try {
const data = await fetchAPI(`/h5/data?file=${filename}`);
const channels = Object.keys(data.data || {});
panel.innerHTML = `
<div class="controls">
${channels.map((ch, i) => `
<button class="channel-btn ${i === 0 ? 'active' : ''}"
onclick="loadChannel('${ch}', this)">
${ch.replace('_', ' ').toUpperCase()}
</button>
`).join('')}
<div class="data-type-toggle">
<button class="active" onclick="setDataType('calibrated', this)">Calibrated</button>
<button onclick="setDataType('raw', this)">Raw</button>
</div>
</div>
<div class="chart-container">
<canvas id="waveformChart"></canvas>
</div>
<div class="stats-bar">
<div class="stat">
<div class="stat-value">${data.metadata?.n_samples?.toLocaleString() || '—'}</div>
<div class="stat-label">Samples</div>
</div>
<div class="stat">
<div class="stat-value">${data.metadata?.sample_rate_hz || '—'}</div>
<div class="stat-label">Sample Rate (Hz)</div>
</div>
<div class="stat">
<div class="stat-value">${formatDuration(data.metadata?.duration_sec)}</div>
<div class="stat-label">Duration</div>
</div>
<div class="stat">
<div class="stat-value" id="statMin">—</div>
<div class="stat-label">Min</div>
</div>
<div class="stat">
<div class="stat-value" id="statMax">—</div>
<div class="stat-label">Max</div>
</div>
</div>
`;
if (channels.length > 0) {
currentChannel = channels[0];
drawWaveform(data.data[currentChannel], data.metadata);
}
} catch (e) {
panel.innerHTML = `<div class="empty-state"><h3>Error</h3><p>${e.message}</p></div>`;
}
}
async function loadChannel(channel, btn) {
currentChannel = channel;
document.querySelectorAll('.channel-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
try {
const data = await fetchAPI(`/h5/data?file=${currentFile}`);
if (data.data[channel]) {
drawWaveform(data.data[channel], data.metadata);
}
} catch (e) {
console.error('Error loading channel:', e);
}
}
function setDataType(type, btn) {
currentDataType = type === 'raw' ? 'raw_data' : 'calibrated_data';
document.querySelectorAll('.data-type-toggle button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Reload current file with new data type
if (currentFile) selectFile(currentFile);
}
function drawWaveform(channelData, metadata) {
const ctx = document.getElementById('waveformChart').getContext('2d');
if (chart) chart.destroy();
// Downsample for display
const maxPoints = 2000;
const step = Math.max(1, Math.floor(channelData.length / maxPoints));
const displayData = [];
const labels = [];
for (let i = 0; i < channelData.length; i += step) {
const slice = channelData.slice(i, Math.min(i + step, channelData.length));
displayData.push({
x: i / (metadata?.sample_rate_hz || 500),
y: slice.reduce((a, b) => a + b, 0) / slice.length
});
}
// Calculate stats
const min = Math.min(...channelData);
const max = Math.max(...channelData);
document.getElementById('statMin').textContent = min.toFixed(2);
document.getElementById('statMax').textContent = max.toFixed(2);
chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: currentChannel,
data: displayData,
borderColor: '#00d9ff',
backgroundColor: 'rgba(0, 217, 255, 0.1)',
fill: true,
pointRadius: 0,
borderWidth: 1.5,
tension: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'index'
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
titleColor: '#00d9ff',
bodyColor: '#fff',
borderColor: '#00d9ff',
borderWidth: 1
}
},
scales: {
x: {
type: 'linear',
title: {
display: true,
text: 'Time (seconds)',
color: '#666'
},
ticks: { color: '#555' },
grid: { color: 'rgba(255,255,255,0.03)' }
},
y: {
title: {
display: true,
text: 'Amplitude',
color: '#666'
},
ticks: { color: '#555' },
grid: { color: 'rgba(255,255,255,0.03)' }
}
}
}
});
}
function formatDuration(sec) {
if (!sec) return '—';
if (sec < 60) return `${sec.toFixed(0)}s`;
if (sec < 3600) return `${Math.floor(sec/60)}m ${Math.floor(sec%60)}s`;
return `${Math.floor(sec/3600)}h ${Math.floor((sec%3600)/60)}m`;
}
// Init
loadFiles();
setInterval(() => checkHealth(), 30000);
</script>
</body>
</html>