From b8d7db91cfd3c804fa511e38601e3a93cd77836a Mon Sep 17 00:00:00 2001 From: Sergey Revyakin Date: Thu, 9 Apr 2026 11:32:05 +0700 Subject: [PATCH] . --- .env.example | 2 +- .gitignore | 3 +- README.md | 8 +- capture_hourly.sh | 36 +- deploy/docker/docker-compose.yml | 28 +- deploy/systemd/dronedetector-compose.service | 4 +- deploy/systemd/dronedetector-sdr-1200.service | 3 +- deploy/systemd/dronedetector-sdr-1500.service | 1 - deploy/systemd/dronedetector-sdr-2400.service | 3 +- deploy/systemd/dronedetector-sdr-3300.service | 1 - deploy/systemd/dronedetector-sdr-433.service | 1 - deploy/systemd/dronedetector-sdr-4500.service | 1 - deploy/systemd/dronedetector-sdr-5200.service | 1 - deploy/systemd/dronedetector-sdr-5800.service | 1 - deploy/systemd/dronedetector-sdr-750.service | 1 - .../systemd/dronedetector-sdr-868-915.service | 19 - deploy/systemd/dronedetector-sdr-868.service | 20 - deploy/systemd/dronedetector-sdr-915.service | 6 +- deploy/systemd/precheck-sdr.sh | 22 - install_all.sh | 4 - ...data_2400.py => compose_send_data_5800.py} | 20 +- orange_scripts/{main_2400.py => main_5800.py} | 0 read_energy.py | 4 +- read_energy_wide.py | 408 ++++++++++++++++++ restart_all.sh | 6 - src/main_1200.py | 4 +- src/main_1500.py | 4 +- src/main_2400.py | 4 +- src/main_3300.py | 4 +- src/main_433.py | 4 +- src/main_4500.py | 4 +- src/main_5200.py | 4 +- src/main_5800.py | 4 +- src/main_750.py | 4 +- src/main_868.py | 99 ----- src/main_868_915_router.py | 122 ------ src/main_915.py | 4 +- src/shared_router_868_915.py | 173 -------- ...mageDatasetCreate_spec_imag_real_915.ipynb | 303 +------------ train_scripts/Training_models_1.2.ipynb | 252 ++--------- 40 files changed, 544 insertions(+), 1048 deletions(-) delete mode 100644 deploy/systemd/dronedetector-sdr-868-915.service delete mode 100644 deploy/systemd/dronedetector-sdr-868.service delete mode 100755 deploy/systemd/precheck-sdr.sh rename orange_scripts/{compose_send_data_2400.py => compose_send_data_5800.py} (91%) rename orange_scripts/{main_2400.py => main_5800.py} (100%) create mode 100644 read_energy_wide.py delete mode 100644 src/main_868.py delete mode 100644 src/main_868_915_router.py delete mode 100644 src/shared_router_868_915.py diff --git a/.env.example b/.env.example index 621ea63..b9117bf 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # GENERAL ################# module_name=dronedetector -freqs=433,750,868,915,1200,2400,3300,4500,5200,5800 +freqs=433,750,868,915,1200,1500,2400,3300,4500,5200,5800 signal_threshold=0.02 signal_threshold_433=0.0195 diff --git a/.gitignore b/.gitignore index d87418a..c710265 100644 --- a/.gitignore +++ b/.gitignore @@ -188,4 +188,5 @@ runtime/ /.venv-*/* -train_scripts/models/* \ No newline at end of file +train_scripts/models/* +train_scripts/models/ensemble_1.2_jpg_*/ \ No newline at end of file diff --git a/README.md b/README.md index 61f3937..918bb24 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,7 @@ sudo systemctl stop dronedetector-sdr-5800.service sudo systemctl stop dronedetector-sdr-1200.service sudo systemctl stop dronedetector-sdr-2400.service sudo systemctl stop dronedetector-sdr-1500.service -sudo systemctl stop dronedetector-sdr-868.service -sudo systemctl stop dronedetector-sdr-868-915.service +sudo systemctl stop dronedetector-sdr-915.service ``` @@ -210,4 +209,7 @@ docker compose -f deploy/docker/docker-compose.yml logs --timestamps dronedetect sudo hackrf_spiflash -w hackrf_one_usb.bin ``` -./.venv-sdr/bin/python scripts_nn/data_saver_headless.py --serial 000 --freq 4500000000 --save-dir /home/sibscience-4/Dataset/3300 --file-tag DJI_3_ --samp-rate 20000000 --split-size 400000 --delay 0.1 --rf-gain 12 --if-gain 30 --bb-gain 36 \ No newline at end of file +./.venv-sdr/bin/python scripts_nn/data_saver_headless.py --serial 000 --freq 4500000000 --save-dir /home/sibscience-4/Dataset/3300 --file-tag DJI_3_ --samp-rate 20000000 --split-size 400000 --delay 0.1 --rf-gain 12 --if-gain 30 --bb-gain 36 + + +.venv-sdr/bin/python read_energy.py \ No newline at end of file diff --git a/capture_hourly.sh b/capture_hourly.sh index 91ac311..f707306 100755 --- a/capture_hourly.sh +++ b/capture_hourly.sh @@ -7,8 +7,8 @@ source .env # НАСТРОЙКИ ############################ -# ЗАМЕНИ на реальную точку монтирования nvme1n1 -BASE_DIR="/mnt/nvme1/dataset" + +BASE_DIR="/home/sibsci/dataset/drone" # Путь к python из venv PYTHON_BIN="${PYTHON_BIN:-$PWD/.venv-sdr/bin/python}" @@ -16,18 +16,19 @@ PYTHON_BIN="${PYTHON_BIN:-$PWD/.venv-sdr/bin/python}" # Путь к headless скрипту SCRIPT_PATH="${SCRIPT_PATH:-$PWD/scripts_nn/data_saver_headless.py}" -# Проверка, что каталог реально на nvme1n1 -EXPECTED_DEVICE_PREFIX="/dev/nvme1n1" + # Лимиты -PER_FREQ_LIMIT_BYTES=$((3 * 1024 * 1024 * 1024)) # 3 GiB на частоту за запуск -TOTAL_LIMIT_BYTES=$((128 * 1024 * 1024 * 1024)) # общий лимит 512 GiB -CYCLE_SECONDS=36 # один цикл в час + +lim_all=70 +PER_FREQ_LIMIT_BYTES=$((7 * 1024 * 1024 * 1024)) # GiB на частоту за запуск +TOTAL_LIMIT_BYTES=$((lim_all * 1024 * 1024 * 1024)) # общий лимит GiB +CYCLE_SECONDS=1 # один цикл в час # Параметры SDR SAMP_RATE="20e6" SPLIT_SIZE="400000" -DELAY="0.1" +DELAY="0.01" RF_GAIN="12" IF_GAIN="30" BB_GAIN="36" @@ -36,7 +37,10 @@ BB_GAIN="36" # ЧАСТОТЫ И SERIAL ИЗ ENV ############################ -ORDER=(433 750 915 1200 2400 3300 4500 5200 5800) +#ORDER=(433 750 915 1200 2400 3300 4500 5200 5800) + +ORDER=(2400) + declare -A SERIAL declare -A FREQ_HZ @@ -47,8 +51,6 @@ FREQ_HZ[433]="433000000" SERIAL[750]="$hack_750" FREQ_HZ[750]="750000000" -SERIAL[868]="$hack_868" -FREQ_HZ[868]="868000000" SERIAL[915]="$hack_915" FREQ_HZ[915]="915000000" @@ -105,14 +107,6 @@ ensure_requirements() { mkdir -p "$BASE_DIR" - local dev - dev="$(df -P "$BASE_DIR" | awk 'NR==2 {print $1}')" - - if [[ "$dev" != ${EXPECTED_DEVICE_PREFIX}* ]]; then - echo "BASE_DIR=$BASE_DIR сейчас находится на $dev, а ожидался ${EXPECTED_DEVICE_PREFIX}*" >&2 - echo "Исправь BASE_DIR на точку монтирования nvme1n1" >&2 - exit 1 - fi } run_one_freq() { @@ -151,7 +145,7 @@ run_one_freq() { cur_total_size="$(total_size_bytes)" if (( cur_total_size >= TOTAL_LIMIT_BYTES )); then - log "Достигнут общий лимит 512 GiB. Останавливаю PID=$pid" + log "Достигнут общий лимит $lim_all GiB. Останавливаю PID=$pid" kill -TERM "$pid" 2>/dev/null || true wait "$pid" 2>/dev/null || true return 2 @@ -190,7 +184,7 @@ main_loop() { total_before="$(total_size_bytes)" if (( total_before >= TOTAL_LIMIT_BYTES )); then - log "Общий размер уже >= 512 GiB, выхожу" + log "Общий размер уже >= GiB, выхожу" break fi diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 197b849..7ea4a09 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -11,13 +11,26 @@ services: - PYTHONPATH=/app - JAMMER_STATE_FILE=/app/runtime/jammer_active.flag working_dir: /app - command: ["python3", "-m", "src.server_to_master"] + command: + - uvicorn + - src.server_to_master:app + - --host + - 0.0.0.0 + - --port + - "5010" + - --reload + - --reload-dir + - /app/src + - --reload-dir + - /app/common restart: unless-stopped ports: - "5010:5010" volumes: - ../../.env:/app/.env:ro - ../../runtime:/app/runtime + - ../../src:/app/src + - ../../common:/app/common networks: - dronedetector-net @@ -62,7 +75,18 @@ services: environment: - PYTHONPATH=/app working_dir: /app - command: ["python3", "-m", "telemetry.telemetry_server"] + command: + - uvicorn + - telemetry.telemetry_server:app + - --host + - 0.0.0.0 + - --port + - "5020" + - --reload + - --reload-dir + - /app/telemetry + - --reload-dir + - /app/common restart: unless-stopped ports: - "5020:5020" diff --git a/deploy/systemd/dronedetector-compose.service b/deploy/systemd/dronedetector-compose.service index 53f9473..dec7d99 100644 --- a/deploy/systemd/dronedetector-compose.service +++ b/deploy/systemd/dronedetector-compose.service @@ -8,9 +8,9 @@ Requires=docker.service Type=oneshot WorkingDirectory=__PROJECT_ROOT__ RemainAfterExit=yes -ExecStart=/usr/bin/docker compose -f __PROJECT_ROOT__/deploy/docker/docker-compose.yml up -d --build +ExecStart=/usr/bin/docker compose -f __PROJECT_ROOT__/deploy/docker/docker-compose.yml up -d ExecStop=/usr/bin/docker compose -f __PROJECT_ROOT__/deploy/docker/docker-compose.yml down -ExecReload=/usr/bin/docker compose -f __PROJECT_ROOT__/deploy/docker/docker-compose.yml up -d --build +ExecReload=/usr/bin/docker compose -f __PROJECT_ROOT__/deploy/docker/docker-compose.yml up -d TimeoutStartSec=0 [Install] diff --git a/deploy/systemd/dronedetector-sdr-1200.service b/deploy/systemd/dronedetector-sdr-1200.service index 832d7a4..7f02d98 100644 --- a/deploy/systemd/dronedetector-sdr-1200.service +++ b/deploy/systemd/dronedetector-sdr-1200.service @@ -10,8 +10,7 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh -ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python orange_scripts/main_1200.py +ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_1200.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-1500.service b/deploy/systemd/dronedetector-sdr-1500.service index ceb90be..c26cb8b 100644 --- a/deploy/systemd/dronedetector-sdr-1500.service +++ b/deploy/systemd/dronedetector-sdr-1500.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_1500.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-2400.service b/deploy/systemd/dronedetector-sdr-2400.service index 20e4c4d..41c23ed 100644 --- a/deploy/systemd/dronedetector-sdr-2400.service +++ b/deploy/systemd/dronedetector-sdr-2400.service @@ -10,8 +10,7 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh -ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python orange_scripts/main_2400.py +ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_2400.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-3300.service b/deploy/systemd/dronedetector-sdr-3300.service index 2ce98a7..332d431 100644 --- a/deploy/systemd/dronedetector-sdr-3300.service +++ b/deploy/systemd/dronedetector-sdr-3300.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_3300.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-433.service b/deploy/systemd/dronedetector-sdr-433.service index 056ba55..8862dfd 100644 --- a/deploy/systemd/dronedetector-sdr-433.service +++ b/deploy/systemd/dronedetector-sdr-433.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_433.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-4500.service b/deploy/systemd/dronedetector-sdr-4500.service index 7634da2..977ff39 100644 --- a/deploy/systemd/dronedetector-sdr-4500.service +++ b/deploy/systemd/dronedetector-sdr-4500.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_4500.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-5200.service b/deploy/systemd/dronedetector-sdr-5200.service index 57dcbdf..7d03d7e 100644 --- a/deploy/systemd/dronedetector-sdr-5200.service +++ b/deploy/systemd/dronedetector-sdr-5200.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_5200.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-5800.service b/deploy/systemd/dronedetector-sdr-5800.service index 80ce01b..bae0720 100644 --- a/deploy/systemd/dronedetector-sdr-5800.service +++ b/deploy/systemd/dronedetector-sdr-5800.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_5800.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-750.service b/deploy/systemd/dronedetector-sdr-750.service index 8eb551b..b7d9d55 100644 --- a/deploy/systemd/dronedetector-sdr-750.service +++ b/deploy/systemd/dronedetector-sdr-750.service @@ -10,7 +10,6 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_750.py Restart=always RestartSec=3 diff --git a/deploy/systemd/dronedetector-sdr-868-915.service b/deploy/systemd/dronedetector-sdr-868-915.service deleted file mode 100644 index 73b8a40..0000000 --- a/deploy/systemd/dronedetector-sdr-868-915.service +++ /dev/null @@ -1,19 +0,0 @@ -[Unit] -Description=DroneDetector SDR Router 868/915 Shared HackRF -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -User=__RUN_USER__ -Group=__RUN_GROUP__ -WorkingDirectory=__PROJECT_ROOT__ -EnvironmentFile=__PROJECT_ROOT__/.env -Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh -ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_868_915_router.py -Restart=always -RestartSec=3 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/systemd/dronedetector-sdr-868.service b/deploy/systemd/dronedetector-sdr-868.service deleted file mode 100644 index 28025d0..0000000 --- a/deploy/systemd/dronedetector-sdr-868.service +++ /dev/null @@ -1,20 +0,0 @@ -[Unit] -Description=DroneDetector SDR Scanner 868 MHz -After=network-online.target dronedetector-sdr-868-915.service -Wants=network-online.target -Requires=dronedetector-sdr-868-915.service - -[Service] -Type=simple -User=__RUN_USER__ -Group=__RUN_GROUP__ -WorkingDirectory=__PROJECT_ROOT__ -EnvironmentFile=__PROJECT_ROOT__/.env -Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh -ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_868.py -Restart=always -RestartSec=3 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/systemd/dronedetector-sdr-915.service b/deploy/systemd/dronedetector-sdr-915.service index ed47e91..90cd4fc 100644 --- a/deploy/systemd/dronedetector-sdr-915.service +++ b/deploy/systemd/dronedetector-sdr-915.service @@ -1,8 +1,7 @@ [Unit] Description=DroneDetector SDR Scanner 915 MHz -After=network-online.target dronedetector-sdr-868-915.service +After=network-online.target Wants=network-online.target -Requires=dronedetector-sdr-868-915.service [Service] Type=simple @@ -11,8 +10,7 @@ Group=__RUN_GROUP__ WorkingDirectory=__PROJECT_ROOT__ EnvironmentFile=__PROJECT_ROOT__/.env Environment=PYTHONPATH=__PROJECT_ROOT__ -ExecStartPre=/usr/local/bin/dronedetector-precheck-sdr.sh -ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python orange_scripts/main_915.py +ExecStart=__PROJECT_ROOT__/.venv-sdr/bin/python src/main_915.py Restart=always RestartSec=3 diff --git a/deploy/systemd/precheck-sdr.sh b/deploy/systemd/precheck-sdr.sh deleted file mode 100755 index 2c0800a..0000000 --- a/deploy/systemd/precheck-sdr.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if ! command -v hackrf_info >/dev/null 2>&1; then - echo "[dronedetector-precheck] hackrf_info not found. Install hackrf-tools/hackrf package." >&2 - exit 1 -fi - -if ! command -v gnuradio-config-info >/dev/null 2>&1; then - echo "[dronedetector-precheck] gnuradio-config-info not found. Install gnuradio." >&2 - exit 1 -fi - -if ! python3 -c "import osmosdr" >/dev/null 2>&1; then - echo "[dronedetector-precheck] Python module osmosdr not importable." >&2 - exit 1 -fi - -if ! hackrf_info 2>/dev/null | grep -q "Found HackRF"; then - echo "[dronedetector-precheck] HackRF device was not detected by hackrf_info." >&2 - exit 1 -fi diff --git a/install_all.sh b/install_all.sh index 8406649..905e9ec 100755 --- a/install_all.sh +++ b/install_all.sh @@ -14,8 +14,6 @@ SOURCE_ARCHIVES=( SDR_UNITS=( dronedetector-sdr-433.service dronedetector-sdr-750.service - dronedetector-sdr-868-915.service - dronedetector-sdr-868.service dronedetector-sdr-1500.service dronedetector-sdr-3300.service dronedetector-sdr-4500.service @@ -204,8 +202,6 @@ build_and_run_compose() { install_systemd_units() { log "Installing systemd units" - install -m 0755 "${PROJECT_ROOT}/deploy/systemd/precheck-sdr.sh" /usr/local/bin/dronedetector-precheck-sdr.sh - local src dst for src in "${PROJECT_ROOT}"/deploy/systemd/*.service; do dst="${SYSTEMD_TARGET_DIR}/$(basename "$src")" diff --git a/orange_scripts/compose_send_data_2400.py b/orange_scripts/compose_send_data_5800.py similarity index 91% rename from orange_scripts/compose_send_data_2400.py rename to orange_scripts/compose_send_data_5800.py index b2eddcc..827b601 100644 --- a/orange_scripts/compose_send_data_2400.py +++ b/orange_scripts/compose_send_data_5800.py @@ -8,13 +8,13 @@ import json import time -load_root_env(__file__) -validate_env("orange_scripts/compose_send_data_2400.py", { - "POROG_2400": as_float, - "SERVER_IP_2": as_str, - "SERVER_PORT_2": as_int, -}) -porog = float(os.getenv('POROG_2400')) +load_root_env(__file__) +validate_env("orange_scripts/compose_send_data_5800.py", { + "POROG_5800": as_float, + "SERVER_IP_2": as_str, + "SERVER_PORT_2": as_int, +}) +porog = float(os.getenv('POROG_5800')) server_ip_1 = os.getenv('SERVER_IP_1') server_port_1 = os.getenv('SERVER_PORT_1') server_ip_2 = os.getenv('SERVER_IP_2') @@ -28,9 +28,9 @@ flag = 0 ############################## # HYPERPARAMETERS ############################## -f_base = 2.4e9 +f_base = 5.9e9 f_step = 20e6 -f_roof = 2.5e9 +f_roof = 5.7e9 ############################## # Variables ############################## @@ -55,7 +55,7 @@ def send_data(sig): print('#' * 10) print('\nОтправка пакета ' + str(token+1)) data_to_send = { - "freq": 2400, + "freq": 5800, "channel": int(channel), "token": int(token+1), "data_real": np.asarray(np.array(sig, dtype=np.complex64).real, dtype=np.float32), diff --git a/orange_scripts/main_2400.py b/orange_scripts/main_5800.py similarity index 100% rename from orange_scripts/main_2400.py rename to orange_scripts/main_5800.py diff --git a/read_energy.py b/read_energy.py index 6bcc56d..781fd50 100755 --- a/read_energy.py +++ b/read_energy.py @@ -69,7 +69,7 @@ class ProbeTop(gr.top_block): try: getattr(self.src, fn)(val, 0) except Exception: - pass + raise Exception("не ставится усиление") try: self.src.set_bandwidth(0, 0) except Exception: @@ -260,7 +260,7 @@ def build_parser() -> argparse.ArgumentParser: p.add_argument("--interval", type=float, default=0.5, help="Per-device read interval (s)") p.add_argument("--refresh", type=float, default=0.5, help="Console refresh interval (s)") p.add_argument("--reopen-delay", type=float, default=1.0, help="Retry delay after BUSY/ERR (s)") - p.add_argument("--gain", type=float, default=16.0, help="General gain") + p.add_argument("--gain", type=float, default=0.0, help="General gain") p.add_argument("--if-gain", type=float, default=16.0, help="IF gain") p.add_argument("--bb-gain", type=float, default=16.0, help="BB gain") return p diff --git a/read_energy_wide.py b/read_energy_wide.py new file mode 100644 index 0000000..2538a1e --- /dev/null +++ b/read_energy_wide.py @@ -0,0 +1,408 @@ +""" +./.venv-sdr/bin/python read_energy_wide.py \ + --serial 0000000000000000a18c63dc2a83b813 \ + --sample-rate 20000000 \ + --base 6000 \ + --roof 5700 \ + --step 20 + +""" +#!/usr/bin/env python3 +import argparse +import math +import re +import signal +import subprocess +import sys +import time +from dataclasses import dataclass +from typing import Dict, List, Optional, Tuple + +try: + import numpy as np +except Exception as exc: + print(f"numpy import failed: {exc}", file=sys.stderr) + sys.exit(1) + +try: + from gnuradio import blocks, gr + import osmosdr +except Exception as exc: + print(f"gnuradio/osmosdr import failed: {exc}", file=sys.stderr) + print("Run with the SDR venv, e.g. .venv-sdr/bin/python read_energy_wide.py", file=sys.stderr) + sys.exit(1) + +EPS = 1e-20 + + +@dataclass +class ScanWindow: + seq: int + start_mhz: float + end_mhz: float + low_mhz: float + high_mhz: float + center_mhz: float + status: str = "INIT" + rms: Optional[float] = None + power_lin: Optional[float] = None + dbfs: Optional[float] = None + samples: int = 0 + updated_at: float = 0.0 + error: str = "" + pass_no: int = 0 + + +class WideProbeTop(gr.top_block): + def __init__( + self, + index: int, + center_freq_hz: float, + sample_rate: float, + vec_len: int, + gain: float, + if_gain: float, + bb_gain: float, + ): + super().__init__("hackrf_energy_wide_probe") + self.probe = blocks.probe_signal_vc(vec_len) + self.stream_to_vec = blocks.stream_to_vector(gr.sizeof_gr_complex * 1, vec_len) + self.src = osmosdr.source(args=f"numchan=1 hackrf={index}") + self.src.set_time_unknown_pps(osmosdr.time_spec_t()) + self.src.set_sample_rate(sample_rate) + self.src.set_center_freq(center_freq_hz, 0) + try: + self.src.set_freq_corr(0, 0) + except Exception: + pass + try: + self.src.set_gain_mode(False, 0) + except Exception: + pass + for fn, val in (("set_gain", gain), ("set_if_gain", if_gain), ("set_bb_gain", bb_gain)): + try: + getattr(self.src, fn)(val, 0) + except Exception: + pass + try: + self.src.set_bandwidth(0, 0) + except Exception: + pass + try: + self.src.set_antenna("", 0) + except Exception: + pass + self.connect((self.src, 0), (self.stream_to_vec, 0)) + self.connect((self.stream_to_vec, 0), (self.probe, 0)) + + def tune(self, freq_hz: float) -> None: + self.src.set_center_freq(freq_hz, 0) + + def read_metrics(self) -> Tuple[float, float, float, int]: + arr = np.asarray(self.probe.level(), dtype=np.complex64) + if arr.size == 0: + raise RuntimeError("no samples") + power_lin = float(np.mean(arr.real * arr.real + arr.imag * arr.imag)) + rms = math.sqrt(max(power_lin, 0.0)) + dbfs = 10.0 * math.log10(max(power_lin, EPS)) + return rms, power_lin, dbfs, int(arr.size) + + def read_window(self, settle: float, avg_reads: int, pause_between_reads: float) -> Tuple[float, float, float, int]: + if settle > 0: + time.sleep(settle) + + read_count = max(1, avg_reads) + powers: List[float] = [] + sample_sizes: List[int] = [] + last_error: Optional[Exception] = None + + for idx in range(read_count): + deadline = time.time() + 1.0 + while True: + try: + _, power_lin, _, samples = self.read_metrics() + powers.append(power_lin) + sample_sizes.append(samples) + break + except Exception as exc: + last_error = exc + if time.time() >= deadline: + raise RuntimeError(str(last_error) if last_error else "no samples") + time.sleep(0.02) + if idx + 1 < read_count and pause_between_reads > 0: + time.sleep(pause_between_reads) + + power_lin = float(sum(powers) / len(powers)) + rms = math.sqrt(max(power_lin, 0.0)) + dbfs = 10.0 * math.log10(max(power_lin, EPS)) + samples = int(sum(sample_sizes) / len(sample_sizes)) + return rms, power_lin, dbfs, samples + + +def parse_hackrf_info() -> Dict[str, int]: + try: + proc = subprocess.run(["hackrf_info"], capture_output=True, text=True, timeout=15) + except FileNotFoundError: + raise RuntimeError("hackrf_info not found") + except subprocess.TimeoutExpired: + raise RuntimeError("hackrf_info timeout") + text = (proc.stdout or "") + "\n" + (proc.stderr or "") + out: Dict[str, int] = {} + cur_idx: Optional[int] = None + for line in text.splitlines(): + m = re.search(r"^Index:\s*(\d+)", line) + if m: + cur_idx = int(m.group(1)) + continue + m = re.search(r"^Serial number:\s*([0-9a-fA-F]+)", line) + if m and cur_idx is not None: + out[m.group(1).lower()] = cur_idx + if not out: + raise RuntimeError("no devices parsed from hackrf_info") + return out + + +def fmt(value: Optional[float], spec: str) -> str: + return "-" if value is None else format(value, spec) + + +def build_windows(base_mhz: float, roof_mhz: float, step_mhz: float) -> List[ScanWindow]: + if step_mhz <= 0: + raise ValueError("step must be > 0") + if base_mhz == roof_mhz: + raise ValueError("base and roof must be different") + + direction = -1.0 if roof_mhz < base_mhz else 1.0 + edge = base_mhz + seq = 1 + windows: List[ScanWindow] = [] + + while True: + next_edge = edge + direction * step_mhz + if direction < 0 and next_edge < roof_mhz: + next_edge = roof_mhz + if direction > 0 and next_edge > roof_mhz: + next_edge = roof_mhz + + low_mhz = min(edge, next_edge) + high_mhz = max(edge, next_edge) + center_mhz = (low_mhz + high_mhz) / 2.0 + windows.append( + ScanWindow( + seq=seq, + start_mhz=edge, + end_mhz=next_edge, + low_mhz=low_mhz, + high_mhz=high_mhz, + center_mhz=center_mhz, + ) + ) + + if next_edge == roof_mhz: + break + edge = next_edge + seq += 1 + + return windows + + +def render( + windows: List[ScanWindow], + serial: str, + index: int, + sample_rate: float, + base_mhz: float, + roof_mhz: float, + step_mhz: float, + started_at: float, + pass_no: int, + current_seq: int, +) -> None: + now = time.time() + capture_bw_mhz = sample_rate / 1e6 + current_row = next((row for row in windows if row.seq == current_seq), None) + best_row = max( + (row for row in windows if row.status == "OK" and row.dbfs is not None), + key=lambda row: row.dbfs if row.dbfs is not None else float("-inf"), + default=None, + ) + print("\x1b[2J\x1b[H", end="") + print("HackRF Wide Energy Monitor (relative power: RMS / linear / dBFS)") + print( + f"serial: {serial} | idx: {index} | sample-rate: {capture_bw_mhz:.3f} MHz | " + f"scan: {base_mhz:.3f}->{roof_mhz:.3f} MHz step {step_mhz:.3f} MHz | " + f"pass: {pass_no} | uptime: {int(now-started_at)}s | {time.strftime('%Y-%m-%d %H:%M:%S')}" + ) + print() + header = ( + f"{'cur':>3} {'seq':>3} {'window MHz':>23} {'center':>9} {'status':>8} " + f"{'dBFS':>9} {'rms':>10} {'power':>12} {'N':>5} {'age':>5} error" + ) + print(header) + print("-" * len(header)) + for row in windows: + age = "-" if row.updated_at <= 0 else f"{(now-row.updated_at):.1f}" + err = row.error + if len(err) > 50: + err = err[:47] + "..." + marker = ">>>" if row.seq == current_seq else "" + print( + f"{marker:>3} {row.seq:>3} " + f"{f'{row.high_mhz:.3f}-{row.low_mhz:.3f}':>23} {row.center_mhz:>9.3f} {row.status:>8} " + f"{fmt(row.dbfs, '.2f'):>9} {fmt(row.rms, '.6f'):>10} {fmt(row.power_lin, '.8f'):>12} " + f"{row.samples:>5} {age:>5} {err}" + ) + print() + if best_row is not None: + best_age = "-" if best_row.updated_at <= 0 else f"{(now-best_row.updated_at):.1f}" + print( + f"{'':>3} {'MAX':>3} " + f"{f'{best_row.high_mhz:.3f}-{best_row.low_mhz:.3f}':>23} {best_row.center_mhz:>9.3f} {best_row.status:>8} " + f"{fmt(best_row.dbfs, '.2f'):>9} {fmt(best_row.rms, '.6f'):>10} {fmt(best_row.power_lin, '.8f'):>12} " + f"{best_row.samples:>5} {best_age:>5} pass={best_row.pass_no}" + ) + elif current_row is not None: + current_age = "-" if current_row.updated_at <= 0 else f"{(now-current_row.updated_at):.1f}" + print( + f"{'':>3} {'MAX':>3} " + f"{f'{current_row.high_mhz:.3f}-{current_row.low_mhz:.3f}':>23} {current_row.center_mhz:>9.3f} {'INIT':>8} " + f"{fmt(None, '.2f'):>9} {fmt(None, '.6f'):>10} {fmt(None, '.8f'):>12} " + f"{0:>5} {current_age:>5} no successful windows yet" + ) + print("Ctrl+C to stop. Window width equals step; sample-rate must be >= step to cover each window.") + sys.stdout.flush() + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Retune one HackRF across a wide frequency range and measure energy") + parser.add_argument("--serial", required=True, help="HackRF serial number from hackrf_info") + parser.add_argument("--sample-rate", type=float, required=True, help="Sample rate in Hz") + parser.add_argument("--base", type=float, required=True, help="Scan start edge in MHz") + parser.add_argument("--roof", type=float, required=True, help="Scan end edge in MHz") + parser.add_argument("--step", type=float, required=True, help="Window width / retune step in MHz") + parser.add_argument("--vec-len", type=int, default=4096, help="Probe vector length") + parser.add_argument("--settle", type=float, default=0.12, help="Wait time after retune before reading (s)") + parser.add_argument("--avg-reads", type=int, default=3, help="How many probe reads to average per window") + parser.add_argument("--pause-between-reads", type=float, default=0.02, help="Pause between averaged reads (s)") + parser.add_argument("--passes", type=int, default=0, help="Number of sweep passes, 0 means infinite") + parser.add_argument("--gain", type=float, default=16.0, help="General gain") + parser.add_argument("--if-gain", type=float, default=16.0, help="IF gain") + parser.add_argument("--bb-gain", type=float, default=16.0, help="BB gain") + return parser + + +def main() -> int: + args = build_parser().parse_args() + serial = args.serial.lower() + + try: + windows = build_windows(args.base, args.roof, args.step) + except ValueError as exc: + print(f"invalid scan range: {exc}", file=sys.stderr) + return 2 + + step_hz = args.step * 1e6 + if args.sample_rate < step_hz: + print( + f"sample-rate {args.sample_rate:.0f} Hz is smaller than step window {step_hz:.0f} Hz; " + "this would leave gaps in the scan", + file=sys.stderr, + ) + return 2 + + try: + serial_to_index = parse_hackrf_info() + except Exception as exc: + print(f"hackrf discovery failed: {exc}", file=sys.stderr) + return 3 + + index = serial_to_index.get(serial) + if index is None: + print(f"serial {serial} not found in hackrf_info", file=sys.stderr) + print("available serials:", file=sys.stderr) + for item_serial, item_index in sorted(serial_to_index.items(), key=lambda item: item[1]): + print(f" idx={item_index} serial={item_serial}", file=sys.stderr) + return 4 + + stop_requested = False + + def on_signal(signum, frame): + nonlocal stop_requested + stop_requested = True + + signal.signal(signal.SIGINT, on_signal) + signal.signal(signal.SIGTERM, on_signal) + + probe: Optional[WideProbeTop] = None + started_at = time.time() + pass_no = 0 + current_seq = windows[0].seq + + try: + probe = WideProbeTop( + index=index, + center_freq_hz=windows[0].center_mhz * 1e6, + sample_rate=args.sample_rate, + vec_len=args.vec_len, + gain=args.gain, + if_gain=args.if_gain, + bb_gain=args.bb_gain, + ) + probe.start() + time.sleep(max(args.settle, 0.12)) + + while not stop_requested: + pass_no += 1 + for row in windows: + if stop_requested: + break + current_seq = row.seq + try: + probe.tune(row.center_mhz * 1e6) + rms, power_lin, dbfs, samples = probe.read_window( + settle=args.settle, + avg_reads=args.avg_reads, + pause_between_reads=args.pause_between_reads, + ) + row.status = "OK" + row.rms = rms + row.power_lin = power_lin + row.dbfs = dbfs + row.samples = samples + row.error = "" + row.updated_at = time.time() + row.pass_no = pass_no + except Exception as exc: + row.status = "ERR" + row.error = str(exc) + row.updated_at = time.time() + render( + windows=windows, + serial=serial, + index=index, + sample_rate=args.sample_rate, + base_mhz=args.base, + roof_mhz=args.roof, + step_mhz=args.step, + started_at=started_at, + pass_no=pass_no, + current_seq=current_seq, + ) + if args.passes > 0 and pass_no >= args.passes: + break + except Exception as exc: + print(f"scanner failed: {exc}", file=sys.stderr) + return 5 + finally: + if probe is not None: + try: + probe.stop() + probe.wait() + except Exception: + pass + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/restart_all.sh b/restart_all.sh index bbb517a..6617f4e 100755 --- a/restart_all.sh +++ b/restart_all.sh @@ -7,8 +7,6 @@ COMPOSE_FILE="${PROJECT_ROOT}/deploy/docker/docker-compose.yml" SDR_UNITS=( dronedetector-sdr-433.service dronedetector-sdr-750.service - #dronedetector-sdr-868-915.service - #dronedetector-sdr-868.service dronedetector-sdr-1500.service dronedetector-sdr-3300.service dronedetector-sdr-4500.service @@ -37,10 +35,6 @@ restart_docker_services() { else log "Compose file not found: $COMPOSE_FILE" fi - - if ${SUDO[@]} systemctl list-unit-files dronedetector-compose.service >/dev/null 2>&1; then - ${SUDO[@]} systemctl restart dronedetector-compose.service || true - fi } restart_sdr_services() { diff --git a/src/main_1200.py b/src/main_1200.py index f665fe2..94f6648 100644 --- a/src/main_1200.py +++ b/src/main_1200.py @@ -40,8 +40,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_1500.py b/src/main_1500.py index ec1def4..dfa0061 100644 --- a/src/main_1500.py +++ b/src/main_1500.py @@ -40,8 +40,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_2400.py b/src/main_2400.py index 8790050..30b9b26 100644 --- a/src/main_2400.py +++ b/src/main_2400.py @@ -40,8 +40,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_3300.py b/src/main_3300.py index c8e01c1..db92d1a 100644 --- a/src/main_3300.py +++ b/src/main_3300.py @@ -94,8 +94,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(100, 0) - self.rtlsdr_source_0.set_if_gain(100, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_433.py b/src/main_433.py index ad47aaf..5e2b394 100644 --- a/src/main_433.py +++ b/src/main_433.py @@ -94,8 +94,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_4500.py b/src/main_4500.py index 0d75120..43f8c3d 100644 --- a/src/main_4500.py +++ b/src/main_4500.py @@ -94,8 +94,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(100, 0) - self.rtlsdr_source_0.set_if_gain(100, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_5200.py b/src/main_5200.py index 3c015df..18c8172 100644 --- a/src/main_5200.py +++ b/src/main_5200.py @@ -93,8 +93,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(100, 0) - self.rtlsdr_source_0.set_if_gain(100, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_5800.py b/src/main_5800.py index d9a566b..bbd2b56 100644 --- a/src/main_5800.py +++ b/src/main_5800.py @@ -94,8 +94,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(100, 0) - self.rtlsdr_source_0.set_if_gain(100, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_750.py b/src/main_750.py index 4e5b976..9c063eb 100644 --- a/src/main_750.py +++ b/src/main_750.py @@ -94,8 +94,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/main_868.py b/src/main_868.py deleted file mode 100644 index 482993a..0000000 --- a/src/main_868.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from gnuradio import blocks -from gnuradio import gr -from gnuradio import zeromq -import signal -import sys -import threading -import time - -import embedded_868 as my_freq - -from common.runtime import load_root_env -from common.shared_stream_addrs import SHARED_868_ADDR, SHARED_VECTOR_LEN - - -load_root_env(__file__) - - -class get_center_freq(gr.top_block): - def __init__(self): - gr.top_block.__init__(self, 'get_center_freq') - - self.prob_freq = 0 - self.poll_rate = 10000 - self.vector_len = SHARED_VECTOR_LEN - self.center_freq = 0 - self.shared_addr = SHARED_868_ADDR - self._stop_polling = threading.Event() - self._prob_freq_thread = None - - self.probSigVec = blocks.probe_signal_vc(self.vector_len) - self.shared_source_0 = zeromq.pull_source( - gr.sizeof_gr_complex, - self.vector_len, - self.shared_addr, - 100, - False, - -1, - False, - ) - self.connect((self.shared_source_0, 0), (self.probSigVec, 0)) - - def start_polling(self): - if self._prob_freq_thread is not None: - return - - def _prob_freq_probe(): - while not self._stop_polling.is_set(): - self.set_prob_freq(self.probSigVec.level()) - time.sleep(1.0 / self.poll_rate) - - self._prob_freq_thread = threading.Thread(target=_prob_freq_probe, daemon=True) - self._prob_freq_thread.start() - - def get_prob_freq(self): - return self.prob_freq - - def set_prob_freq(self, prob_freq): - self.prob_freq = prob_freq - self.center_freq = my_freq.work(self.prob_freq) - - def get_center_freq(self): - return self.center_freq - - def set_center_freq(self, center_freq): - self.center_freq = center_freq - - def close(self): - self._stop_polling.set() - self.stop() - self.wait() - - -def main(top_block_cls=get_center_freq, options=None): - tb = top_block_cls() - - def sig_handler(sig=None, frame=None): - tb.close() - sys.exit(0) - - signal.signal(signal.SIGINT, sig_handler) - signal.signal(signal.SIGTERM, sig_handler) - - tb.start() - tb.start_polling() - try: - print('shared_pull_addr:', SHARED_868_ADDR) - print('debug_flag:', my_freq.debug_flag) - print('save_data_flag:', my_freq.save_data_flag) - print('send_to_module_flag:', my_freq.send_to_module_flag) - except EOFError: - pass - tb.wait() - - -if __name__ == '__main__': - main() diff --git a/src/main_868_915_router.py b/src/main_868_915_router.py deleted file mode 100644 index cb62ec4..0000000 --- a/src/main_868_915_router.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from gnuradio import blocks -from gnuradio import gr -import signal -import sys -import threading -import time - -import osmosdr - -from common.runtime import load_root_env, resolve_hackrf_index -from common.shared_stream_addrs import SHARED_VECTOR_LEN -from shared_router_868_915 import SharedRouter868915 - - -load_root_env(__file__) - - -def get_hack_id(): - return resolve_hackrf_index('hack_868', 'src/main_868_915_router.py') - - -class get_center_freq(gr.top_block): - def __init__(self): - gr.top_block.__init__(self, 'get_center_freq') - - self.prob_freq = 0 - self.poll_rate = 10000 - self.vector_len = SHARED_VECTOR_LEN - self.router = SharedRouter868915() - self.active_lane = self.router.get_active_name() - self.center_freq = self.router.get_start_freq() - self._stop_polling = threading.Event() - self._prob_freq_thread = None - - self.probSigVec = blocks.probe_signal_vc(self.vector_len) - self.rtlsdr_source_0 = osmosdr.source( - args='numchan=' + str(1) + ' ' + 'hackrf=' + get_hack_id() - ) - self.rtlsdr_source_0.set_time_unknown_pps(osmosdr.time_spec_t()) - self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_antenna('', 0) - self.rtlsdr_source_0.set_min_output_buffer(self.vector_len) - self.apply_active_frontend() - self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) - - self.blocks_stream_to_vector_1 = blocks.stream_to_vector(gr.sizeof_gr_complex * 1, self.vector_len) - self.connect((self.rtlsdr_source_0, 0), (self.blocks_stream_to_vector_1, 0)) - self.connect((self.blocks_stream_to_vector_1, 0), (self.probSigVec, 0)) - - def start_polling(self): - if self._prob_freq_thread is not None: - return - - def _prob_freq_probe(): - while not self._stop_polling.is_set(): - self.set_prob_freq(self.probSigVec.level()) - time.sleep(1.0 / self.poll_rate) - - self._prob_freq_thread = threading.Thread(target=_prob_freq_probe, daemon=True) - self._prob_freq_thread.start() - - def apply_active_frontend(self): - frontend = self.router.get_active_frontend() - self.rtlsdr_source_0.set_sample_rate(frontend['sample_rate']) - self.rtlsdr_source_0.set_gain(frontend['gain'], 0) - self.rtlsdr_source_0.set_if_gain(frontend['if_gain'], 0) - self.rtlsdr_source_0.set_bb_gain(frontend['bb_gain'], 0) - self.rtlsdr_source_0.set_bandwidth(frontend['bandwidth'], 0) - - def get_prob_freq(self): - return self.prob_freq - - def set_prob_freq(self, prob_freq): - self.prob_freq = prob_freq - next_center, lane_switched = self.router.route_vector(self.prob_freq) - if lane_switched: - self.active_lane = self.router.get_active_name() - self.apply_active_frontend() - if next_center != self.center_freq: - self.set_center_freq(next_center) - - def get_center_freq(self): - return self.center_freq - - def set_center_freq(self, center_freq): - self.center_freq = center_freq - self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) - - def close(self): - self._stop_polling.set() - try: - self.router.close() - finally: - self.stop() - self.wait() - - -def main(top_block_cls=get_center_freq, options=None): - tb = top_block_cls() - - def sig_handler(sig=None, frame=None): - tb.close() - sys.exit(0) - - signal.signal(signal.SIGINT, sig_handler) - signal.signal(signal.SIGTERM, sig_handler) - - tb.start() - tb.start_polling() - try: - print('shared_router_active_lane:', tb.router.get_active_name()) - print('shared_router_start_freq:', tb.get_center_freq()) - except EOFError: - pass - tb.wait() - - -if __name__ == '__main__': - main() diff --git a/src/main_915.py b/src/main_915.py index 03918a8..18018f1 100644 --- a/src/main_915.py +++ b/src/main_915.py @@ -40,8 +40,8 @@ class get_center_freq(gr.top_block): self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_center_freq(self.center_freq, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) - self.rtlsdr_source_0.set_gain(24, 0) - self.rtlsdr_source_0.set_if_gain(24, 0) + self.rtlsdr_source_0.set_gain(16, 0) + self.rtlsdr_source_0.set_if_gain(16, 0) self.rtlsdr_source_0.set_bb_gain(100, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) diff --git a/src/shared_router_868_915.py b/src/shared_router_868_915.py deleted file mode 100644 index e94ae41..0000000 --- a/src/shared_router_868_915.py +++ /dev/null @@ -1,173 +0,0 @@ -import os - -import numpy as np -import zmq - -from common.runtime import load_root_env -from common.shared_stream_addrs import SHARED_868_ADDR, SHARED_915_ADDR -from core.multichannelswitcher import MultiChannel -from core.sig_n_medi_collect import Signal - - -load_root_env(__file__) - - -class Scheduler868: - def __init__(self): - self.signal_length = int(os.getenv('signal_length_868')) - self.multi_channel = MultiChannel( - [*map(float, os.getenv('f_step_868').split())], - [*map(float, os.getenv('f_bases_868').split())], - [*map(float, os.getenv('f_roofs_868').split())], - ) - self.base_freq = float(self.multi_channel.init_f()) - self.signal = Signal() - - def get_current_freq(self): - return float(self.multi_channel.get_cur_channel()) - - def process(self, lvl): - current_before = self.get_current_freq() - metric = self.signal.fill_signal(lvl, self.signal_length) - if metric == 0: - return current_before, False - - next_freq = float(self.multi_channel.change_channel()) - self.signal.clear() - lane_complete = next_freq == self.base_freq and current_before != self.base_freq - return next_freq, lane_complete - - -class Scheduler915: - def __init__(self): - self.porog = float(os.getenv('POROG_915')) - self.point_amount = 100_000 - self.split_size = 400_000 - self.show_amount = int(0.8 * self.point_amount) - self.f_base = 0.91e9 - self.f_step = 20e6 - self.f_roof = 0.98e9 - self.f = self.f_base - self.channel = 1 - self.flag = 0 - self.signal_arr = np.array([], dtype=np.complex64) - - def get_current_freq(self): - return float(self.f) - - def _median(self, sig): - samples = np.asarray(np.abs(np.array(sig, dtype=np.complex64)), dtype=np.float32) - sorted_samples = sorted(samples) - median = abs(float(np.median(sorted_samples[self.show_amount:]))) - self.flag = 0 if self.porog > median else 1 - - def _advance(self): - next_freq = self.f + self.f_step - if next_freq >= self.f_roof: - self.f = self.f_base - self.channel = 1 - return float(self.f), True - - self.f = next_freq - self.channel += 1 - return float(self.f), False - - def process(self, lvl): - y = np.asarray(lvl, dtype=np.complex64).ravel() - self.signal_arr = np.concatenate((self.signal_arr, y), axis=None) - - if self.flag == 0 and len(self.signal_arr) >= self.point_amount: - self._median(self.signal_arr[:self.point_amount]) - self.signal_arr = np.array([], dtype=np.complex64) - if self.flag == 0: - return self._advance() - - if len(self.signal_arr) >= self.split_size: - self.flag = 0 - self.signal_arr = np.array([], dtype=np.complex64) - return self._advance() - - return float(self.f), False - - -class SharedRouter868915: - def __init__(self): - self.frontends = { - '868': { - 'sample_rate': 20e6, - 'gain': 24, - 'if_gain': 24, - 'bb_gain': 100, - 'bandwidth': 0, - }, - '915': { - 'sample_rate': 20e6, - 'gain': 16, - 'if_gain': 16, - 'bb_gain': 0, - 'bandwidth': 0, - }, - } - self.schedulers = { - '868': Scheduler868(), - '915': Scheduler915(), - } - self.context = zmq.Context.instance() - self.sockets = { - '868': self._make_socket(SHARED_868_ADDR), - '915': self._make_socket(SHARED_915_ADDR), - } - self.active_name = '868' - self.switch_counter = 0 - - def _make_socket(self, addr): - sock = self.context.socket(zmq.PUSH) - sock.setsockopt(zmq.SNDHWM, 64) - sock.setsockopt(zmq.LINGER, 0) - sock.setsockopt(zmq.IMMEDIATE, 1) - sock.bind(addr) - return sock - - def close(self): - for sock in self.sockets.values(): - try: - sock.close(0) - except Exception: - pass - - def get_active_name(self): - return self.active_name - - def get_active_freq(self): - return self.schedulers[self.active_name].get_current_freq() - - def get_active_frontend(self): - return dict(self.frontends[self.active_name]) - - def get_start_freq(self): - return self.get_active_freq() - - def route_vector(self, lvl): - vector = np.asarray(lvl, dtype=np.complex64).ravel() - active_name = self.active_name - - try: - self.sockets[active_name].send(vector.tobytes(), zmq.NOBLOCK) - except zmq.Again: - pass - - next_freq, lane_complete = self.schedulers[active_name].process(vector) - lane_switched = False - - if lane_complete: - previous = self.active_name - self.active_name = '915' if self.active_name == '868' else '868' - next_freq = self.get_active_freq() - self.switch_counter += 1 - lane_switched = True - print( - f'[shared-router-868-915] switch#{self.switch_counter}: ' - f'{previous} -> {self.active_name}, tune={next_freq}' - ) - - return float(next_freq), lane_switched diff --git a/train_scripts/ImageDatasetCreate_spec_imag_real_915.ipynb b/train_scripts/ImageDatasetCreate_spec_imag_real_915.ipynb index e5a98e9..818581c 100644 --- a/train_scripts/ImageDatasetCreate_spec_imag_real_915.ipynb +++ b/train_scripts/ImageDatasetCreate_spec_imag_real_915.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "4fdb98fc-65bb-467e-be0c-168fee9b0fca", "metadata": {}, "outputs": [ @@ -16,10 +16,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -64,7 +64,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "4848b066-2e09-4c1c-b8fa-8e3fa84d907a", "metadata": {}, "outputs": [], @@ -74,7 +74,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "9267fbe1", "metadata": {}, "outputs": [], @@ -147,20 +147,20 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "448da74a-e0ae-44d8-9877-8dd1f257a24f", "metadata": {}, "outputs": [], "source": [ - "selected_freq=750\n", + "selected_freq=915\n", "\n", - "path_to_binaries = f'/mnt/nvme1/dataset/{selected_freq}'\n", - "path_to_pictures = f'/mnt/nvme1/dataset_img/noise/{selected_freq}_jpg'" + "path_to_binaries = f'/home/sibsci/dataset/drone/{selected_freq}'\n", + "path_to_pictures = f'/home/sibsci/dataset_img/drone/{selected_freq}_jpg'" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "ac4945a8-29c4-4da4-945f-08658953e3e5", "metadata": {}, "outputs": [], @@ -170,7 +170,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 8, "id": "6f226f86-5d72-4573-8af6-750128b70263", "metadata": {}, "outputs": [ @@ -178,287 +178,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "2026-03-25_16-59-25: 0%| | 0/40 [00:00 \u001b[39m\u001b[32m53\u001b[39m path_res, model_name = prepare_and_learning_detection(num_classes = num_classes, num_samples = 1000, path_dataset = \"/home/sibsci/dataset_img\", \n\u001b[32m 54\u001b[39m selected_freq=\u001b[32m5800\u001b[39m,model_name = config_name+\u001b[33m\"_5.8_jpg_\"\u001b[39m, config_name = config_name, model=model)\n\u001b[32m 55\u001b[39m \n\u001b[32m 56\u001b[39m \n", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[8]\u001b[39m\u001b[32m, line 102\u001b[39m, in \u001b[36mprepare_and_learning_detection\u001b[39m\u001b[34m(num_classes, num_samples, path_dataset, model_name, config_name, model, selected_freq)\u001b[39m\n\u001b[32m 98\u001b[39m \n\u001b[32m 99\u001b[39m dataset = MyDataset(path_dataset=path_res, csv_file=\u001b[33m'dataset.csv'\u001b[39m)\n\u001b[32m 100\u001b[39m train_set, valid_set = torch.utils.data.random_split(dataset, [\u001b[32m0.7\u001b[39m, \u001b[32m0.3\u001b[39m], generator=torch.Generator().manual_seed(\u001b[32m42\u001b[39m))\n\u001b[32m 101\u001b[39m batch_size = config.batch_size\n\u001b[32m--> \u001b[39m\u001b[32m102\u001b[39m train_dataloader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=\u001b[38;5;28;01mTrue\u001b[39;00m, drop_last=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 103\u001b[39m valid_dataloader = torch.utils.data.DataLoader(valid_set, batch_size=batch_size, shuffle=\u001b[38;5;28;01mTrue\u001b[39;00m, drop_last=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 104\u001b[39m \n\u001b[32m 105\u001b[39m dataloaders = {}\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/DroneDetector/.venv-train/lib/python3.12/site-packages/torch/utils/data/dataloader.py:394\u001b[39m, in \u001b[36mDataLoader.__init__\u001b[39m\u001b[34m(self, dataset, batch_size, shuffle, sampler, batch_sampler, num_workers, collate_fn, pin_memory, drop_last, timeout, worker_init_fn, multiprocessing_context, generator, prefetch_factor, persistent_workers, pin_memory_device, in_order)\u001b[39m\n\u001b[32m 392\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m: \u001b[38;5;66;03m# map-style\u001b[39;00m\n\u001b[32m 393\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m shuffle:\n\u001b[32m--> \u001b[39m\u001b[32m394\u001b[39m sampler = \u001b[43mRandomSampler\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdataset\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgenerator\u001b[49m\u001b[43m=\u001b[49m\u001b[43mgenerator\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;66;03m# type: ignore[arg-type]\u001b[39;00m\n\u001b[32m 395\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 396\u001b[39m sampler = SequentialSampler(dataset) \u001b[38;5;66;03m# type: ignore[arg-type]\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/DroneDetector/.venv-train/lib/python3.12/site-packages/torch/utils/data/sampler.py:149\u001b[39m, in \u001b[36mRandomSampler.__init__\u001b[39m\u001b[34m(self, data_source, replacement, num_samples, generator)\u001b[39m\n\u001b[32m 144\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[32m 145\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mreplacement should be a boolean value, but got replacement=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m.replacement\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 146\u001b[39m )\n\u001b[32m 148\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\u001b[38;5;28mself\u001b[39m.num_samples, \u001b[38;5;28mint\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;28mself\u001b[39m.num_samples <= \u001b[32m0\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m149\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 150\u001b[39m \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mnum_samples should be a positive integer value, but got num_samples=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m.num_samples\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 151\u001b[39m )\n", + "\u001b[31mValueError\u001b[39m: num_samples should be a positive integer value, but got num_samples=0" ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "train Loss: 0.0002 Acc: 0.9956\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|██████████| 72/72 [00:11<00:00, 6.11it/s]\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "val Loss: 0.0002 Acc: 1.0000\n", - "Total time elapsed = 493.41988416798995 seconds\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAUC5JREFUeJzt3Xl0VPX9//HnTEISEpKwJywRVMIeEtYQQNZAUGqNG5FSNmn9agGxqFVENjfcaFFBKFqB9ieCWKEUEQgREAVkCasiKmWJQlgUEhIggcz9/THMwJgASZjkzkxej3PumTt3PvfO+061eXnv+95rMQzDQERERKQCsZpdgIiIiEh5UwASERGRCkcBSERERCocBSARERGpcBSAREREpMJRABIREZEKRwFIREREKhwFIBEREalwFIBERESkwlEAEhERkQpHAUhEbtjcuXOxWCxs3brV7FKKZceOHfz+978nKiqKwMBAqlevTmJiInPmzKGgoMDs8kSkHPibXYCISHl69913efjhh4mIiGDQoEFER0dz5swZ0tLSGD58OEePHuWZZ54xu0wRKWMKQCJSYWzatImHH36YhIQEli9fTmhoqPOzxx57jK1bt7Jnzx63fFdubi4hISFu2ZaIuJ9OgYlIudm+fTu33347YWFhVKlShV69erFp0yaXMRcuXGDy5MlER0cTFBREjRo16NKlC6mpqc4xmZmZDBs2jPr16xMYGEidOnW46667OHjw4DW/f/LkyVgsFt5//32X8OPQrl07hg4dCsDatWuxWCysXbvWZczBgwexWCzMnTvXuWzo0KFUqVKF/fv3c8cddxAaGsrAgQMZOXIkVapU4ezZs4W+a8CAAURGRrqccvv000+57bbbCAkJITQ0lH79+vH1119fc59EpHQUgESkXHz99dfcdttt7Ny5k7/85S+MHz+eAwcO0L17d7766ivnuEmTJjF58mR69OjB9OnTGTduHDfddBPp6enOMffeey+LFy9m2LBhvP322zz66KOcOXOGw4cPX/X7z549S1paGl27duWmm25y+/5dvHiRpKQkateuzeuvv869995LSkoKubm5fPLJJ4Vq+e9//8t9992Hn58fAP/617/o168fVapU4ZVXXmH8+PF88803dOnS5brBTkRKwRARuUFz5swxAGPLli1XHZOcnGwEBAQY+/fvdy47cuSIERoaanTt2tW5LDY21ujXr99Vt3Pq1CkDMF577bUS1bhz504DMEaPHl2s8WvWrDEAY82aNS7LDxw4YADGnDlznMuGDBliAMbTTz/tMtZmsxn16tUz7r33XpflH374oQEYn3/+uWEYhnHmzBmjatWqxh//+EeXcZmZmUZ4eHih5SJy43QESETKXEFBAatWrSI5OZlbbrnFubxOnTr87ne/44svviA7OxuAqlWr8vXXX/P9998Xua3KlSsTEBDA2rVrOXXqVLFrcGy/qFNf7vLII4+4vLdYLNx///0sX76cnJwc5/KFCxdSr149unTpAkBqaiqnT59mwIABnDx50jn5+fkRHx/PmjVryqxmkYpKAUhEytyJEyc4e/YsTZo0KfRZs2bNsNlsZGRkAPDcc89x+vRpGjduTExMDE8++SS7du1yjg8MDOSVV17h008/JSIigq5du/Lqq6+SmZl5zRrCwsIAOHPmjBv37DJ/f3/q169faHlKSgrnzp1j6dKlAOTk5LB8+XLuv/9+LBYLgDPs9ezZk1q1arlMq1at4vjx42VSs0hFpgAkIh6la9eu7N+/n/fee4+WLVvy7rvv0qZNG959913nmMcee4zvvvuOKVOmEBQUxPjx42nWrBnbt2+/6nYbNWqEv78/u3fvLlYdjnDya1e7T1BgYCBWa+H/S+3YsSMNGzbkww8/BOC///0v586dIyUlxTnGZrMB9j6g1NTUQtN//vOfYtUsIsWnACQiZa5WrVoEBwezb9++Qp99++23WK1WoqKinMuqV6/OsGHD+OCDD8jIyKBVq1ZMmjTJZb1bb72Vxx9/nFWrVrFnzx7y8/OZOnXqVWsIDg6mZ8+efP75586jTddSrVo1AE6fPu2y/NChQ9dd99f69+/PihUryM7OZuHChTRs2JCOHTu67AtA7dq1SUxMLDR17969xN8pItemACQiZc7Pz48+ffrwn//8x+WKpmPHjjF//ny6dOniPEX1888/u6xbpUoVGjVqRF5eHmC/gur8+fMuY2699VZCQ0OdY65m4sSJGIbBoEGDXHpyHLZt28a8efMAaNCgAX5+fnz++ecuY95+++3i7fQVUlJSyMvLY968eaxYsYL+/fu7fJ6UlERYWBgvvfQSFy5cKLT+iRMnSvydInJtuhGiiLjNe++9x4oVKwotHz16NC+88AKpqal06dKFP/3p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sHr1agDCw8MLbSM3N5ekpCQSEhLYsmULx48f5w9/+AMjR450CXlr1qyhTp06rFmzhh9++IGUlBTi4uL4YyluTGez2ZzhZ926dVy8eJERI0aQkpLi/Fs+cOBAWrduzcyZM/Hz82PHjh1UqlQJgBEjRpCfn8/nn39OSEgI33zzDVWqVClxHSViSCFZWVkGYGRlZZXL933yiWGAYTRoYBg2W7l8pYhImTh37pzxzTffGOfOnbMvyMmx/x+cGVNOTrHr3rt3rwEYa9ascS677bbbjN///vdXXadfv37G448/7nzfrVs3Y/To0c73DRo0MP72t78ZhmEYK1euNPz9/Y2ffvrJ+fmnn35qAMbixYuv+h2vvfaa0bZtW+f7iRMnGrGxsYXGXbmd2bNnG9WqVTNyrtj/Tz75xLBarUZmZqZhGIYxZMgQo0GDBsbFixedY+6//34jJSXlqrXMmTPHCA8PL/KzVatWGX5+fsbhw4edy77++msDMDZv3mwYhmGEhoYac+fOLXL9mJgYY9KkSVf97isV+mfsCiX5++1Tl8F7q27dICDA/kiM7783uxoRkYqnadOmdOrUiffeew+AH374gfXr1zN8+HAACgoKeP7554mJiaF69epUqVKFlStXFvs5k3v37iUqKoq6des6lxV1wc/ChQvp3LkzkZGRVKlShWeffbbEz7Lcu3cvsbGxhISEOJd17twZm83mcpFQixYt8PPzc76vU6fOVR9FVZzvjIqKcrmPXvPmzalatSp79+4F7H2/f/jDH0hMTOTll19m//79zrGPPvooL7zwAp07d2bixImlajovKQUgDxASAo4WJV0NJiI+JTgYcnLMmYKDS1Tq8OHD+fe//82ZM2eYM2cOt956K926dQPgtdde44033uCpp55izZo17Nixg6SkJPLz8932U23cuJGBAwdyxx13sGzZMrZv3864cePc+h1Xcpx+crBYLM776ZWFSZMm8fXXX9OvXz8+++wzmjdvzuLFiwH4wx/+wP/+9z8GDRrE7t27adeuHW+99VaZ1QIKQB7DcTWY7gckIj7FYrH/V54ZUzH6f67Uv39/rFYr8+fP55///CcPPvigsx/oyy+/5K677uL3v/89sbGx3HLLLXz33XfF3nazZs3IyMhwubBn06ZNLmM2bNhAgwYNGDduHO3atSM6OppDv3padkBAAAUFBdf9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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkkAAAHHCAYAAACr0swBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAASbFJREFUeJzt3Xl4Dvf+//HXnci+CSILEUs1QYnTWBot6jQVSx1LVDg0obTVojRHW0pLq6qLqrW6HEuLllqr9cVJY2k5WooopZbQIhKhJRsics/vD7/ex92MJRpu0ufjuua63J/7fc+8Z5pz3a8z87lnLIZhGAIAAIAdJ0c3AAAAcCsiJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAEAAJggJAG4qnfffVcWi0VNmzZ1dCu3pePHj2vo0KGKiIiQp6envLy8FBUVpVdffVWnT592dHsALsPCs9sAXM29996rY8eO6eeff9b+/ft1xx13OLql28aWLVvUrl075eXlqVevXoqKipIkff/995o/f76aNWum//znPw7uEoAZQhKAKzp06JBq1qypJUuW6IknntCAAQM0atQoR7dlKj8/X15eXo5uw+b06dO66667dOHCBa1bt04RERF27x8/flwffvihRo4c+ae3davtO1AWcLkNwBXNmzdP/v7+at++vbp27ap58+aZ1p0+fVrPPPOMqlevLjc3N1WtWlUJCQk6efKkrebcuXMaPXq07rzzTrm7uys4OFhdunRRWlqaJGndunWyWCxat26d3bp//vlnWSwWzZ492zbWu3dveXt7Ky0tTe3atZOPj4969uwpSfrmm2/08MMPq1q1anJzc1NoaKieeeYZnT17tljfP/30k7p166aAgAB5eHgoPDxcI0aMkCStXbtWFotFS5cuLfa5Tz75RBaLRZs2bbrssXv//feVnp6uCRMmFAtIkhQYGGgXkCwWi0aPHl2srnr16urdu7ft9ezZs2WxWLR+/Xo99dRTqly5sqpWrapFixbZxs16sVgs2rVrl92+d+3aVRUqVJC7u7saNWqk5cuXX3Z/gL+aco5uAMCtbd68eerSpYtcXV3Vo0cPTZ8+XVu2bFHjxo1tNXl5eWrevLn27NmjRx99VHfffbdOnjyp5cuX6+jRo6pUqZKKior00EMPKSUl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t29fLV68WLm5uZo1a5Zq1apl+1J96623NGnSJD3//PNau3atUlNTFRsbq/Pnz5faodq0aZN69uypdu3a6csvv9T27ds1YsSIUt3Gpf4YZCwWiy3smFm9erXS09MVHx+vcuXKqVy5curevbt++eUX26/PPTw8Lvv5K70nXZxeI12cjP27y82RujQAStLQoUO1dOlSvfbaa/rmm2+Umpqq+vXr247d1bYtSf369dP8+fN19uxZzZo1S/Hx8Td84n2JQ9Lhw4dVo0aNYuNhYWElTtMAAAezWCQvL8cs1zAf6VLdunWTk5OTPvnkE3388cd69NFHbfOTNm7cqI4dO6pXr16KjIxUzZo1tW/fvmted506dXTkyBFlZGTYxr799lu7mv/+978KCwvTiBEj1KhRI9WuXVu//PKLXY2rq+tVnzxRp04d7dixQ/n5+baxjRs3ysnJSeHh4dfc8x/NmDFD3bt3V2pqqt3SvXt32wTuBg0a6JtvvjENNz4+Pqpevfplb+cTEBAgSXbH6NJJ3FeyceNG9e7dW507d1b9+vUVFBSkn3/+2fZ+/fr1ZbVatX79+suuo127dvLy8tL06dO1atUqPfroo9e07T+jxCGpcuXKpvej2LFjhypWrFgqTQEA8Efe3t6Kj4/X8OHDlZGRod69e9veq127tpKTk/Xf//5Xe/bs0RNPPKHjx49f87pjYmJ05513KjExUTt27NA333yjESNG2NXUrl1bhw8f1vz585WWlqbJkycXewpF9erVdejQIaWmpurkyZOm9ynq2bOn3N3dlZiYqF27dmnt2rUaNGiQHnnkEQUGBpbsoPx/J06c0BdffKHExETddddddktCQoKWLVum3377TQMHDlROTo66d++u77//Xvv379ecOXNsl/lGjx6tt99+W5MnT9b+/fu1bds2TZkyRdLFsz333HOPXn/9de3Zs0fr16/XyJEjr6m/2rVra8mSJUpNTdWOHTv0z3/+0+6sWPXq1ZWYmKhHH31Uy5Yt06FDh7Ru3Tp99tlnthpnZ2f17t1bw4cPV+3atU0vh5a2EoekHj166Omnn9batWtVVFSkoqIirVmzRoMHD1b37t1vRI8AAEi6eMnt1KlTio2NtZs/NHLkSN19992KjY3V/fffr6CgoBLd3drJyUlLly7V2bNn1aRJE/Xr109jx461q/nHP/6hZ555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+d0333yjvXv3ql+/flfsdfjw4crOzrYtR44cuY49BgAAtwuHhiRXV1dFRUXZTZa2Wq1KSUlRdHT0FT/r7u6uKlWq6MKFC1q8eLE6duxYrMbLy0vBwcE6deqUVq9ebVozY8YMRUVFKTIy8orbc3Nzk6+vr90CAADKrnKObiApKUmJiYlq1KiRmjRpookTJyo/P199+vSRJCUkJKhKlSoaN26cJOm7775Tenq6GjZsqPT0dI0ePVpWq1XPPfecbZ2rV6+WYRgKDw/XgQMH9OyzzyoiIsK2zt/l5ORo4cKFevvtt2/eDgMAgNuCw0NSfHy8Tpw4oZdeekmZmZlq2LChVq1aZZvMffjwYbu5ROfOndPIkSN18OBBeXt7q127dpozZ47Kly9vq8nOztbw4cN19OhRVahQQXFxcRo7dqxcXFzstj1//nwZhqEePXrclH0FAAC3D4ffJ+l2xX2SAAC4/dw290kCAAC4VRGSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATBCSAAAATDg8JE2bNk3Vq1eXu7u7mjZtqs2bN1+2trCwUK+88opq1aold3d3RUZGatWqVXY1ubm5GjJkiMLCwuTh4aFmzZppy5Ytxda1Z88e/eMf/5Cfn5+8vLzUuHFjHT58uNT3DwAA3J4cGpIWLFigpKQkjRo1Stu2bVNkZKRiY2OVlZVlWj9y5Ei9//77mjJlinbv3q3+/furc+fO2r59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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "67" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -623,8 +439,8 @@ "\n", "#----------Создания датасета и обучение модели--------------\n", "\n", - "path_res, model_name = prepare_and_learning_detection(num_classes = num_classes, num_samples = 10000, path_dataset = \"/mnt/nvme1/dataset_img\", \n", - " selected_freq=1200,model_name = config_name+\"_1.2_jpg_\", config_name = config_name, model=model)\n", + "path_res, model_name = prepare_and_learning_detection(num_classes = num_classes, num_samples = 1000, path_dataset = \"/home/sibsci/dataset_img\", \n", + " selected_freq=5800,model_name = config_name+\"_5.8_jpg_\", config_name = config_name, model=model)\n", "\n", "\n", "torch.cuda.empty_cache()\n",