This commit is contained in:
Lukáš Trkan
2026-05-01 21:42:32 +02:00
parent 8df624f568
commit 2bd731620c
5 changed files with 435 additions and 185 deletions

View File

@@ -8,14 +8,24 @@
"\n",
"Složíme otagované dlaždice (z notebooku 03) do velkých leteckých snímků HK pro **dvě vrstvy**:\n",
"\n",
"| Vrstva | Zdroj dlaždic | Výstup (preview) | Výstup (plný) |\n",
"|--------|--------------|-----------------|--------------|\n",
"| Finetuned model | `tiles_annotated_finetuned/` | `hk_finetuned_preview.jpg` | `hk_finetuned_full.jpg` |\n",
"| Base model | `tiles_annotated_base/` | `hk_base_preview.jpg` | `hk_base_full.jpg` |\n",
"| Vrstva | Zdroj dlaždic | Výstup (plný) |\n",
"|--------|--------------|--------------|\n",
"| Finetuned model | `tiles_annotated_finetuned/` | `hk_finetuned_full.jpg` |\n",
"| Base model | `tiles_annotated_base/` | `hk_base_full.jpg` |\n",
"\n",
"Výsledná mapa: **74 × 69 dlaždic × 256 px = 18 944 × 17 664 px**"
]
},
{
"cell_type": "markdown",
"id": "md-a222d4",
"metadata": {},
"source": [
"## Příprava mřížky dlaždic\n",
"\n",
"Názvy dlaždic kódují jejich pozici ve formátu `18_X_Y.jpg` (tile schema XYZ, zoom 18). Z těchto souřadnic odvodíme rozměry výsledné mapy: počet sloupců, řádků a výsledné rozlišení v pixelech."
]
},
{
"cell_type": "code",
"execution_count": 1,
@@ -46,9 +56,7 @@
" assert d.exists(), f\"Nejprve spusť 03_inference_tiles.ipynb! (chybí {d})\"\n",
"\n",
"TILE_SIZE = 256\n",
"PREVIEW_SCALE = 4\n",
"\n",
"# Rozsah mřížky odvoď z finetuned (obě mají stejné dlaždice)\n",
"tile_files = sorted(ANNOTATED_DIR_FINETUNED.glob(\"18_*.jpg\"))\n",
"xs, ys = [], []\n",
"for f in tile_files:\n",
@@ -68,82 +76,49 @@
"print(f\"Odhadovaná velikost jedné mapy (JPEG 88%): ~{W*H*3/1e6/10:.0f} MB\")"
]
},
{
"cell_type": "markdown",
"id": "md-a8c48b",
"metadata": {},
"source": [
"## Sestavení map\n",
"\n",
"Pro obě vrstvy (finetuned a base model) složíme dlaždice do jednoho velkého obrázku."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Dostupná RAM: 6.8 GB\n",
"Potřeba pro jednu plnou mapu: 1.0 GB (dvě: 2.0 GB)\n",
"RAM dostačuje — generuji plné mapy pro obě vrstvy\n"
]
}
],
"source": [
"import psutil\n",
"\n",
"ram_gb = psutil.virtual_memory().available / 1e9\n",
"needed_gb = W * H * 3 / 1e9 # jedna mapa; dvě = 2×\n",
"\n",
"print(f\"Dostupná RAM: {ram_gb:.1f} GB\")\n",
"print(f\"Potřeba pro jednu plnou mapu: {needed_gb:.1f} GB (dvě: {2*needed_gb:.1f} GB)\")\n",
"\n",
"BUILD_FULL = needed_gb <= ram_gb * 0.5 # nech puffer pro druhou mapu\n",
"if BUILD_FULL:\n",
" print(\"RAM dostačuje — generuji plné mapy pro obě vrstvy\")\n",
"else:\n",
" print(\"Nedostatek RAM — generuji pouze preview mapy\")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a375151f0f8f4fd5ac94ecb5567c9849",
"model_id": "08bd8aac266e47808fd9018d7788e249",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Preview finetuned: 0%| | 0/5106 [00:00<?, ?it/s]"
"Plná mapa finetuned: 0%| | 0/5106 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Uloženo: hk_finetuned_preview.jpg (4736×4416 px)\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "8f83829d65db4bb9b3399d07711f726e",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Preview base: 0%| | 0/5106 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Uloženo: hk_base_preview.jpg (4736×4416 px)\n"
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 34\u001b[39m\n\u001b[32m 30\u001b[39m print()\n\u001b[32m 31\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m canvas\n\u001b[32m 32\u001b[39m \n\u001b[32m 33\u001b[39m \n\u001b[32m---> \u001b[39m\u001b[32m34\u001b[39m assemble_map(ANNOTATED_DIR_FINETUNED, Path(\"output/hk_finetuned_full.jpg\"),\n\u001b[32m 35\u001b[39m scale=\u001b[32m1\u001b[39m, desc=\u001b[33m\"Plná mapa finetuned\"\u001b[39m)\n\u001b[32m 36\u001b[39m assemble_map(ANNOTATED_DIR_BASE, Path(\"output/hk_base_full.jpg\"),\n\u001b[32m 37\u001b[39m scale=\u001b[32m1\u001b[39m, desc=\u001b[33m\"Plná mapa base\"\u001b[39m)\n",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 26\u001b[39m, in \u001b[36massemble_map\u001b[39m\u001b[34m(annotated_dir, output_path, scale, desc)\u001b[39m\n\u001b[32m 22\u001b[39m canvas.paste(tile, (xi * ts, yi * ts))\n\u001b[32m 23\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m Exception:\n\u001b[32m 24\u001b[39m missing += \u001b[32m1\u001b[39m\n\u001b[32m 25\u001b[39m \n\u001b[32m---> \u001b[39m\u001b[32m26\u001b[39m canvas.save(output_path, quality=\u001b[32m88\u001b[39m, optimize=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 27\u001b[39m print(f\"Uloženo: {output_path} ({pw}×{ph} px)\", end=\u001b[33m\"\"\u001b[39m)\n\u001b[32m 28\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m missing:\n\u001b[32m 29\u001b[39m print(f\" [{missing} chybějících dlaždic]\", end=\u001b[33m\"\"\u001b[39m)\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/workspace/su2-2/.venv/lib/python3.14/site-packages/PIL/Image.py:2713\u001b[39m, in \u001b[36mImage.save\u001b[39m\u001b[34m(self, fp, format, **params)\u001b[39m\n\u001b[32m 2710\u001b[39m fp = cast(IO[\u001b[38;5;28mbytes\u001b[39m], fp)\n\u001b[32m 2712\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m2713\u001b[39m \u001b[43msave_handler\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfp\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfilename\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2714\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m:\n\u001b[32m 2715\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m open_fp:\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/workspace/su2-2/.venv/lib/python3.14/site-packages/PIL/JpegImagePlugin.py:847\u001b[39m, in \u001b[36m_save\u001b[39m\u001b[34m(im, fp, filename)\u001b[39m\n\u001b[32m 842\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 843\u001b[39m \u001b[38;5;66;03m# The EXIF info needs to be written as one block, + APP1, + one spare byte.\u001b[39;00m\n\u001b[32m 844\u001b[39m \u001b[38;5;66;03m# Ensure that our buffer is big enough. Same with the icc_profile block.\u001b[39;00m\n\u001b[32m 845\u001b[39m bufsize = \u001b[38;5;28mmax\u001b[39m(\u001b[38;5;28mlen\u001b[39m(exif) + \u001b[32m5\u001b[39m, \u001b[38;5;28mlen\u001b[39m(extra) + \u001b[32m1\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m847\u001b[39m \u001b[43mImageFile\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_save\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 848\u001b[39m \u001b[43m \u001b[49m\u001b[43mim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfp\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m[\u001b[49m\u001b[43mImageFile\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_Tile\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mjpeg\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m(\u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\u001b[43m+\u001b[49m\u001b[43m \u001b[49m\u001b[43mim\u001b[49m\u001b[43m.\u001b[49m\u001b[43msize\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrawmode\u001b[49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbufsize\u001b[49m\n\u001b[32m 849\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/workspace/su2-2/.venv/lib/python3.14/site-packages/PIL/ImageFile.py:665\u001b[39m, in \u001b[36m_save\u001b[39m\u001b[34m(im, fp, tile, bufsize)\u001b[39m\n\u001b[32m 663\u001b[39m fh = fp.fileno()\n\u001b[32m 664\u001b[39m fp.flush()\n\u001b[32m--> \u001b[39m\u001b[32m665\u001b[39m \u001b[43m_encode_tile\u001b[49m\u001b[43m(\u001b[49m\u001b[43mim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfp\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbufsize\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfh\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 666\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (\u001b[38;5;167;01mAttributeError\u001b[39;00m, io.UnsupportedOperation) \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 667\u001b[39m _encode_tile(im, fp, tile, bufsize, \u001b[38;5;28;01mNone\u001b[39;00m, exc)\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/workspace/su2-2/.venv/lib/python3.14/site-packages/PIL/ImageFile.py:700\u001b[39m, in \u001b[36m_encode_tile\u001b[39m\u001b[34m(im, fp, tile, bufsize, fh, exc)\u001b[39m\n\u001b[32m 697\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 698\u001b[39m \u001b[38;5;66;03m# slight speedup: compress to real file object\u001b[39;00m\n\u001b[32m 699\u001b[39m \u001b[38;5;28;01massert\u001b[39;00m fh \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m700\u001b[39m errcode = \u001b[43mencoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mencode_to_file\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfh\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbufsize\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 701\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m errcode < \u001b[32m0\u001b[39m:\n\u001b[32m 702\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m _get_oserror(errcode, encoder=\u001b[38;5;28;01mTrue\u001b[39;00m) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mexc\u001b[39;00m\n",
"\u001b[31mKeyboardInterrupt\u001b[39m: "
]
}
],
@@ -181,75 +156,25 @@
" return canvas\n",
"\n",
"\n",
"# --- Preview mapy (vždy) ---\n",
"preview_ft = assemble_map(ANNOTATED_DIR_FINETUNED, Path(\"output/hk_finetuned_preview.jpg\"),\n",
" scale=PREVIEW_SCALE, desc=\"Preview finetuned\")\n",
"preview_base = assemble_map(ANNOTATED_DIR_BASE, Path(\"output/hk_base_preview.jpg\"),\n",
" scale=PREVIEW_SCALE, desc=\"Preview base\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "348dac183a834470b930654ef862a4f2",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Plná mapa finetuned: 0%| | 0/5106 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Uloženo: hk_finetuned_full.jpg (18944×17664 px)\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "dd4e5219605a4b1a99b9eb0f3f2e0cc4",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Plná mapa base: 0%| | 0/5106 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Uloženo: hk_base_full.jpg (18944×17664 px)\n"
]
}
],
"source": [
"# --- Plné mapy (pokud je dost RAM) ---\n",
"if BUILD_FULL:\n",
" assemble_map(ANNOTATED_DIR_FINETUNED, Path(\"output/hk_finetuned_full.jpg\"),\n",
"assemble_map(ANNOTATED_DIR_FINETUNED, Path(\"output/hk_finetuned_full.jpg\"),\n",
" scale=1, desc=\"Plná mapa finetuned\")\n",
" assemble_map(ANNOTATED_DIR_BASE, Path(\"output/hk_base_full.jpg\"),\n",
" scale=1, desc=\"Plná mapa base\")\n",
"else:\n",
" print(\"Plné mapy přeskočeny (nedostatek RAM).\")"
"assemble_map(ANNOTATED_DIR_BASE, Path(\"output/hk_base_full.jpg\"),\n",
" scale=1, desc=\"Plná mapa base\")"
]
},
{
"cell_type": "markdown",
"id": "md-3f3428",
"metadata": {},
"source": [
"## Srovnání map vedle sebe\n",
"\n",
"Zobrazíme preview mapy obou modelů vedle sebe s legendou barev vozidel. Vizuální srovnání odhalí rozdíly v citlivosti modelů na různé části města."
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -275,16 +200,16 @@
"]\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(28, 13))\n",
"pw = W // PREVIEW_SCALE\n",
"ph = H // PREVIEW_SCALE\n",
"pw = W\n",
"ph = H\n",
"\n",
"for ax, img_path, title in zip(\n",
" axes,\n",
" [\"output/hk_finetuned_preview.jpg\", \"output/hk_base_preview.jpg\"],\n",
" [\"output/hk_finetuned_full.jpg\", \"output/hk_base_full.jpg\"],\n",
" [\"Finetuned model\", \"Base model (před fine-tuningem)\"],\n",
"):\n",
" ax.imshow(Image.open(img_path))\n",
" ax.set_title(f\"Hradec Králové — {title}\\n(preview {pw}×{ph} px)\", fontsize=13)\n",
" ax.set_title(f\"Hradec Králové — {title}\\n({pw}×{ph} px)\", fontsize=13)\n",
" ax.axis(\"off\")\n",
" ax.legend(handles=legend_elements, loc=\"lower right\", fontsize=10,\n",
" framealpha=0.8, title=\"Typy vozidel\")\n",
@@ -294,9 +219,19 @@
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "md-0db3ae",
"metadata": {},
"source": [
"## Souhrn počtů vozidel\n",
"\n",
"Z JSON souborů s detekcemi spočítáme celkový počet vozidel každé třídy pro oba modely a zobrazíme tabulku."
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -360,9 +295,19 @@
"print(\"=\" * 52)"
]
},
{
"cell_type": "markdown",
"id": "md-ba2e9f",
"metadata": {},
"source": [
"## Srovnávací sloupcový graf\n",
"\n",
"Graf porovnává počty detekovaných vozidel podle třídy pro oba modely. Vidíme, které třídy detekuje finetuned model lépe a kde je naopak méně citlivý."
]
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -377,7 +322,6 @@
}
],
"source": [
"# Srovnávací sloupcový graf: finetuned vs base\n",
"bar_colors = [\"#00DC00\", \"#FFDC00\", \"#DC0000\", \"#0078FF\"]\n",
"x = np.arange(len(CLASS_NAMES))\n",
"width = 0.35\n",
@@ -405,6 +349,134 @@
"plt.savefig(\"output/vehicle_counts_comparison.png\", dpi=150, bbox_inches=\"tight\")\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "md-webserver",
"metadata": {},
"source": [
"## Interaktivní mapa\n",
"\n",
"Spustíme jednoduchý HTTP server, který zpřístupní interaktivní mapu v prohlížeči.\n",
"\n",
"Po spuštění buňky otevři odkaz níže v prohlížeči."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "code-webserver",
"metadata": {},
"outputs": [
{
"ename": "OSError",
"evalue": "[Errno 48] Address already in use",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mOSError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 62\u001b[39m\n\u001b[32m 58\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[33m\"_tile_server\"\u001b[39m \u001b[38;5;28;01min\u001b[39;00m globals() \u001b[38;5;28;01mand\u001b[39;00m _tile_server \u001b[38;5;28;01mis\u001b[39;00m \u001b[38;5;28;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 59\u001b[39m _tile_server.shutdown()\n\u001b[32m 60\u001b[39m print(\u001b[33m\"Předchozí server zastaven.\"\u001b[39m)\n\u001b[32m 61\u001b[39m \n\u001b[32m---> \u001b[39m\u001b[32m62\u001b[39m _tile_server = http.server.HTTPServer((\u001b[33m\"\"\u001b[39m, PORT), TileHandler)\n\u001b[32m 63\u001b[39m thread = threading.Thread(target=_tile_server.serve_forever, daemon=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 64\u001b[39m thread.start()\n\u001b[32m 65\u001b[39m \n",
"\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.14/3.14.4/Frameworks/Python.framework/Versions/3.14/lib/python3.14/socketserver.py:457\u001b[39m, in \u001b[36mTCPServer.__init__\u001b[39m\u001b[34m(self, server_address, RequestHandlerClass, bind_and_activate)\u001b[39m\n\u001b[32m 455\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m bind_and_activate:\n\u001b[32m 456\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m457\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mserver_bind\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 458\u001b[39m \u001b[38;5;28mself\u001b[39m.server_activate()\n\u001b[32m 459\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m:\n",
"\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.14/3.14.4/Frameworks/Python.framework/Versions/3.14/lib/python3.14/http/server.py:148\u001b[39m, in \u001b[36mHTTPServer.server_bind\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 146\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mserver_bind\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m 147\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Override server_bind to store the server name.\"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m148\u001b[39m \u001b[43msocketserver\u001b[49m\u001b[43m.\u001b[49m\u001b[43mTCPServer\u001b[49m\u001b[43m.\u001b[49m\u001b[43mserver_bind\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 149\u001b[39m host, port = \u001b[38;5;28mself\u001b[39m.server_address[:\u001b[32m2\u001b[39m]\n\u001b[32m 150\u001b[39m \u001b[38;5;28mself\u001b[39m.server_name = socket.getfqdn(host)\n",
"\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.14/3.14.4/Frameworks/Python.framework/Versions/3.14/lib/python3.14/socketserver.py:478\u001b[39m, in \u001b[36mTCPServer.server_bind\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 473\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\n\u001b[32m 474\u001b[39m \u001b[38;5;28mself\u001b[39m.allow_reuse_port \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mhasattr\u001b[39m(socket, \u001b[33m\"\u001b[39m\u001b[33mSO_REUSEPORT\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 475\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m.address_family \u001b[38;5;129;01min\u001b[39;00m (socket.AF_INET, socket.AF_INET6)\n\u001b[32m 476\u001b[39m ):\n\u001b[32m 477\u001b[39m \u001b[38;5;28mself\u001b[39m.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, \u001b[32m1\u001b[39m)\n\u001b[32m--> \u001b[39m\u001b[32m478\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msocket\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbind\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mserver_address\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 479\u001b[39m \u001b[38;5;28mself\u001b[39m.server_address = \u001b[38;5;28mself\u001b[39m.socket.getsockname()\n",
"\u001b[31mOSError\u001b[39m: [Errno 48] Address already in use"
]
}
],
"source": [
"import threading\n",
"import http.server\n",
"import webbrowser\n",
"import re\n",
"from pathlib import Path\n",
"\n",
"PORT = 8765\n",
"ROOT = Path(\".\").parent.resolve()\n",
"\n",
"TILE_DIRS = {\n",
" \"tiles\": ROOT / \"tiles\",\n",
" \"tiles_annotated_finetuned\": ROOT / \"output\" / \"tiles_annotated_finetuned\",\n",
" \"tiles_annotated_base\": ROOT / \"output\" / \"tiles_annotated_base\",\n",
"}\n",
"\n",
"class TileHandler(http.server.BaseHTTPRequestHandler):\n",
" def log_message(self, *args):\n",
" pass # potlač výpisy každého requestu\n",
"\n",
" def do_GET(self):\n",
" path = self.path.split(\"?\")[0]\n",
"\n",
" # /tiles_annotated_finetuned/18/X/Y.jpg → flat file 18_X_Y.jpg\n",
" m = re.match(r\"^/([^/]+)/(\\d+)/(\\d+)/(\\d+)\\.jpg$\", path)\n",
" if m:\n",
" prefix, z, x, y = m.group(1), m.group(2), m.group(3), m.group(4)\n",
" tile_dir = TILE_DIRS.get(prefix)\n",
" if tile_dir:\n",
" tile_file = tile_dir / f\"{z}_{x}_{y}.jpg\"\n",
" if tile_file.exists():\n",
" data = tile_file.read_bytes()\n",
" self.send_response(200)\n",
" self.send_header(\"Content-Type\", \"image/jpeg\")\n",
" self.send_header(\"Content-Length\", str(len(data)))\n",
" self.end_headers()\n",
" self.wfile.write(data)\n",
" return\n",
" self.send_error(404)\n",
" return\n",
"\n",
" # /output/map.html nebo / → static file\n",
" if path == \"/\" or path == \"\":\n",
" path = \"/output/map.html\"\n",
" file_path = ROOT / path.lstrip(\"/\")\n",
" if file_path.exists() and file_path.is_file():\n",
" suffix = file_path.suffix.lower()\n",
" ct = {\"html\": \"text/html\", \"css\": \"text/css\", \"js\": \"application/javascript\"}.get(suffix[1:], \"application/octet-stream\")\n",
" data = file_path.read_bytes()\n",
" self.send_response(200)\n",
" self.send_header(\"Content-Type\", ct)\n",
" self.send_header(\"Content-Length\", str(len(data)))\n",
" self.end_headers()\n",
" self.wfile.write(data)\n",
" else:\n",
" self.send_error(404)\n",
"\n",
"# Zastav předchozí instanci serveru (pokud existuje)\n",
"if \"_tile_server\" in globals() and _tile_server is not None:\n",
" _tile_server.shutdown()\n",
" print(\"Předchozí server zastaven.\")\n",
"\n",
"_tile_server = http.server.HTTPServer((\"\", PORT), TileHandler)\n",
"thread = threading.Thread(target=_tile_server.serve_forever, daemon=True)\n",
"thread.start()\n",
"\n",
"url = f\"http://localhost:{PORT}\"\n",
"print(f\"Server běží na {url}\")\n",
"print(f\"Otevři v prohlížeči: {url}\")\n",
"webbrowser.open(url)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Server neběží.\n"
]
}
],
"source": [
"# Zastav server\n",
"if \"_tile_server\" in globals() and _tile_server is not None:\n",
" _tile_server.shutdown()\n",
" _tile_server = None\n",
" print(\"Server zastaven.\")\n",
"else:\n",
" print(\"Server neběží.\")\n"
]
}
],
"metadata": {
@@ -428,4 +500,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}