Repair project layout and fix Lab024B spectrum detection
This commit is contained in:
450
tests/lab009_real_photo_estimate.py
Normal file
450
tests/lab009_real_photo_estimate.py
Normal file
@@ -0,0 +1,450 @@
|
||||
"""
|
||||
Lab009. Расчёт параметров передачи реальной фотографии.
|
||||
|
||||
Программа:
|
||||
1. Читает реальный JPEG-файл.
|
||||
2. Определяет его разрешение и размер.
|
||||
3. Разбивает файл на фрагменты.
|
||||
4. Формирует реальные пакеты протокола.
|
||||
5. Учитывает ACK для каждого фрагмента.
|
||||
6. Рассчитывает время передачи при разных скоростях канала.
|
||||
|
||||
Расчёт времени показывает чистое эфирное время без учёта:
|
||||
- пауз между пакетами;
|
||||
- обработки на передатчике и приёмнике;
|
||||
- ожидания ACK;
|
||||
- повторных передач;
|
||||
- преамбулы и служебных символов физического уровня.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from protocol.image_fragments import (
|
||||
FRAGMENT_HEADER_SIZE,
|
||||
encode_image_fragment,
|
||||
split_image_bytes,
|
||||
)
|
||||
|
||||
from protocol.packet import (
|
||||
CRC_SIZE,
|
||||
HEADER_SIZE,
|
||||
MESSAGE_TYPE_ACK,
|
||||
MESSAGE_TYPE_IMAGE_FRAGMENT,
|
||||
build_packet,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Настройки
|
||||
# ============================================================
|
||||
|
||||
SOURCE_PATH = Path(
|
||||
"data/raw/lab009_source.jpg"
|
||||
)
|
||||
|
||||
IMAGE_ID = 20260713
|
||||
|
||||
# Сколько байтов JPEG помещается в один фрагмент.
|
||||
FRAGMENT_DATA_SIZE = 512
|
||||
|
||||
# Скорости полезного цифрового канала в кбит/с.
|
||||
BITRATES_KBPS = [
|
||||
1,
|
||||
5,
|
||||
10,
|
||||
20,
|
||||
50,
|
||||
100,
|
||||
200,
|
||||
]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Вспомогательные функции
|
||||
# ============================================================
|
||||
|
||||
def format_bytes(byte_count: int) -> str:
|
||||
"""
|
||||
Представить размер одновременно в байтах и килобайтах.
|
||||
"""
|
||||
|
||||
kibibytes = byte_count / 1024
|
||||
|
||||
return (
|
||||
f"{byte_count} байт "
|
||||
f"({kibibytes:.2f} КиБ)"
|
||||
)
|
||||
|
||||
|
||||
def format_duration(seconds: float) -> str:
|
||||
"""
|
||||
Представить длительность в удобной форме.
|
||||
"""
|
||||
|
||||
if seconds < 1:
|
||||
return f"{seconds * 1000:.1f} мс"
|
||||
|
||||
if seconds < 60:
|
||||
return f"{seconds:.2f} с"
|
||||
|
||||
minutes = int(seconds // 60)
|
||||
remaining_seconds = seconds % 60
|
||||
|
||||
return (
|
||||
f"{minutes} мин "
|
||||
f"{remaining_seconds:.1f} с"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Проверка исходного файла
|
||||
# ============================================================
|
||||
|
||||
if not SOURCE_PATH.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Не найден файл: {SOURCE_PATH}"
|
||||
)
|
||||
|
||||
source_bytes = SOURCE_PATH.read_bytes()
|
||||
|
||||
if not source_bytes:
|
||||
raise ValueError(
|
||||
"Исходный JPEG-файл пуст"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Чтение параметров изображения
|
||||
# ============================================================
|
||||
|
||||
with Image.open(SOURCE_PATH) as image:
|
||||
|
||||
image_format = image.format
|
||||
image_mode = image.mode
|
||||
image_width, image_height = image.size
|
||||
|
||||
# Проверяем, что файл действительно читается как изображение.
|
||||
image.verify()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Разбиение JPEG на фрагменты
|
||||
# ============================================================
|
||||
|
||||
fragments = split_image_bytes(
|
||||
image_bytes=source_bytes,
|
||||
image_id=IMAGE_ID,
|
||||
fragment_data_size=FRAGMENT_DATA_SIZE,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Формирование DATA-пакетов
|
||||
# ============================================================
|
||||
|
||||
data_packets = []
|
||||
|
||||
for fragment in fragments:
|
||||
|
||||
fragment_payload = encode_image_fragment(
|
||||
fragment
|
||||
)
|
||||
|
||||
packet = build_packet(
|
||||
payload=fragment_payload,
|
||||
message_type=MESSAGE_TYPE_IMAGE_FRAGMENT,
|
||||
sequence_number=fragment.fragment_index,
|
||||
)
|
||||
|
||||
data_packets.append(packet)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Формирование типового ACK-пакета
|
||||
# ============================================================
|
||||
|
||||
ack_packet = build_packet(
|
||||
payload=b"",
|
||||
message_type=MESSAGE_TYPE_ACK,
|
||||
sequence_number=0,
|
||||
)
|
||||
|
||||
ack_packet_size = len(ack_packet)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Расчёт объёмов
|
||||
# ============================================================
|
||||
|
||||
source_size = len(source_bytes)
|
||||
|
||||
fragment_count = len(fragments)
|
||||
|
||||
# Полный размер всех DATA-пакетов.
|
||||
total_data_packet_bytes = sum(
|
||||
len(packet)
|
||||
for packet in data_packets
|
||||
)
|
||||
|
||||
# Для каждого DATA-пакета ожидается один ACK.
|
||||
total_ack_bytes = (
|
||||
fragment_count
|
||||
* ack_packet_size
|
||||
)
|
||||
|
||||
# Идеальный радиообмен:
|
||||
# каждый DATA и каждый ACK переданы ровно один раз.
|
||||
total_radio_bytes = (
|
||||
total_data_packet_bytes
|
||||
+ total_ack_bytes
|
||||
)
|
||||
|
||||
total_radio_bits = (
|
||||
total_radio_bytes
|
||||
* 8
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Детализация служебных расходов
|
||||
# ============================================================
|
||||
|
||||
total_fragment_header_bytes = (
|
||||
fragment_count
|
||||
* FRAGMENT_HEADER_SIZE
|
||||
)
|
||||
|
||||
data_protocol_overhead = (
|
||||
fragment_count
|
||||
* (HEADER_SIZE + CRC_SIZE)
|
||||
)
|
||||
|
||||
total_overhead_bytes = (
|
||||
total_radio_bytes
|
||||
- source_size
|
||||
)
|
||||
|
||||
payload_efficiency = (
|
||||
source_size
|
||||
/ total_radio_bytes
|
||||
* 100
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Вывод параметров фотографии
|
||||
# ============================================================
|
||||
|
||||
print("=== Lab009. Расчёт передачи реальной фотографии ===")
|
||||
|
||||
print("\nИсходный файл:")
|
||||
print(SOURCE_PATH)
|
||||
|
||||
print("\nФормат изображения:")
|
||||
print(image_format)
|
||||
|
||||
print("\nЦветовой режим:")
|
||||
print(image_mode)
|
||||
|
||||
print("\nРазрешение:")
|
||||
print(
|
||||
image_width,
|
||||
"x",
|
||||
image_height,
|
||||
"пикселей",
|
||||
)
|
||||
|
||||
print("\nРазмер исходного JPEG:")
|
||||
print(
|
||||
format_bytes(source_size)
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Вывод структуры передачи
|
||||
# ============================================================
|
||||
|
||||
print("\n--- Фрагментация ---")
|
||||
|
||||
print(
|
||||
"Размер данных одного полного фрагмента:",
|
||||
FRAGMENT_DATA_SIZE,
|
||||
"байт",
|
||||
)
|
||||
|
||||
print(
|
||||
"Количество фрагментов:",
|
||||
fragment_count,
|
||||
)
|
||||
|
||||
print(
|
||||
"Размер заголовка фрагмента:",
|
||||
FRAGMENT_HEADER_SIZE,
|
||||
"байт",
|
||||
)
|
||||
|
||||
print(
|
||||
"Размер заголовка основного пакета:",
|
||||
HEADER_SIZE,
|
||||
"байт",
|
||||
)
|
||||
|
||||
print(
|
||||
"Размер CRC-32:",
|
||||
CRC_SIZE,
|
||||
"байт",
|
||||
)
|
||||
|
||||
print(
|
||||
"Размер ACK-пакета:",
|
||||
ack_packet_size,
|
||||
"байт",
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Вывод полного объёма
|
||||
# ============================================================
|
||||
|
||||
print("\n--- Объём передачи без повторов ---")
|
||||
|
||||
print("Исходные данные JPEG:")
|
||||
print(
|
||||
format_bytes(source_size)
|
||||
)
|
||||
|
||||
print("\nЗаголовки фрагментов:")
|
||||
print(
|
||||
format_bytes(total_fragment_header_bytes)
|
||||
)
|
||||
|
||||
print("\nСлужебные поля и CRC DATA-пакетов:")
|
||||
print(
|
||||
format_bytes(data_protocol_overhead)
|
||||
)
|
||||
|
||||
print("\nВсе DATA-пакеты:")
|
||||
print(
|
||||
format_bytes(total_data_packet_bytes)
|
||||
)
|
||||
|
||||
print("\nВсе ACK-пакеты:")
|
||||
print(
|
||||
format_bytes(total_ack_bytes)
|
||||
)
|
||||
|
||||
print("\nПолный радиообмен DATA + ACK:")
|
||||
print(
|
||||
format_bytes(total_radio_bytes)
|
||||
)
|
||||
|
||||
print("\nОбщие служебные расходы:")
|
||||
print(
|
||||
format_bytes(total_overhead_bytes)
|
||||
)
|
||||
|
||||
print(
|
||||
"\nПолезная эффективность протокола:"
|
||||
)
|
||||
|
||||
print(
|
||||
f"{payload_efficiency:.2f} %"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Расчёт времени передачи
|
||||
# ============================================================
|
||||
|
||||
print("\n--- Чистое эфирное время ---")
|
||||
|
||||
print(
|
||||
"Скорость DATA без ACK DATA + ACK"
|
||||
)
|
||||
|
||||
for bitrate_kbps in BITRATES_KBPS:
|
||||
|
||||
bitrate_bits_per_second = (
|
||||
bitrate_kbps
|
||||
* 1000
|
||||
)
|
||||
|
||||
data_time_seconds = (
|
||||
total_data_packet_bytes
|
||||
* 8
|
||||
/ bitrate_bits_per_second
|
||||
)
|
||||
|
||||
full_time_seconds = (
|
||||
total_radio_bits
|
||||
/ bitrate_bits_per_second
|
||||
)
|
||||
|
||||
print(
|
||||
f"{bitrate_kbps:>4} кбит/с"
|
||||
f" "
|
||||
f"{format_duration(data_time_seconds):>12}"
|
||||
f" "
|
||||
f"{format_duration(full_time_seconds):>12}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Сценарий с повторами
|
||||
# ============================================================
|
||||
|
||||
# Коэффициент 2 означает, что суммарно передано
|
||||
# примерно в два раза больше байтов из-за ошибок,
|
||||
# потерь и повторных передач.
|
||||
RETRY_TRAFFIC_MULTIPLIER = 2.0
|
||||
|
||||
print(
|
||||
"\n--- Оценка при удвоении трафика из-за повторов ---"
|
||||
)
|
||||
|
||||
for bitrate_kbps in BITRATES_KBPS:
|
||||
|
||||
bitrate_bits_per_second = (
|
||||
bitrate_kbps
|
||||
* 1000
|
||||
)
|
||||
|
||||
estimated_seconds = (
|
||||
total_radio_bits
|
||||
* RETRY_TRAFFIC_MULTIPLIER
|
||||
/ bitrate_bits_per_second
|
||||
)
|
||||
|
||||
print(
|
||||
f"{bitrate_kbps:>4} кбит/с"
|
||||
f" "
|
||||
f"{format_duration(estimated_seconds)}"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Автоматические проверки
|
||||
# ============================================================
|
||||
|
||||
assert fragment_count > 0
|
||||
|
||||
assert (
|
||||
total_data_packet_bytes
|
||||
> source_size
|
||||
)
|
||||
|
||||
assert (
|
||||
total_radio_bytes
|
||||
> total_data_packet_bytes
|
||||
)
|
||||
|
||||
assert (
|
||||
0 < payload_efficiency < 100
|
||||
)
|
||||
|
||||
print(
|
||||
"\nПроверка пройдена: "
|
||||
"параметры передачи фотографии рассчитаны."
|
||||
)
|
||||
Reference in New Issue
Block a user