Skip to content

Commit

Permalink
Merge commit '7d3da52ab91454d616022637ce9e3f7692998d5e'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas committed Sep 14, 2022
2 parents ca57d8a + 7d3da52 commit a7867cc
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
sat_img.tiff
.venv
__pycache__
.vscode/*launch.json
mask.npy
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [


{
"name": "Python: Aktuelle Datei",
"name": "Python: params",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["P:\\cytech\\Cytech_Underground_Map\\Cytech_Underground_Terrain\\source\\Images\\layers.cfg", "P:\\cytech\\Cytech_Underground_Map\\Cytech_Underground_Terrain\\source\\Images\\Mask\\mask_underground.tiff"]
"args": ["P:\\cytech\\Cytech_Underground_Map\\Cytech_Underground_Terrain\\source\\Images\\layers.cfg", "P:\\cytech\\Cytech_Underground_Map\\Cytech_Underground_Terrain\\source\\Images\\Mask\\mask_underground.tiff", "-rgbv", "10", "10", "10", "-rgbt", "0.5"]
// "P:\cytech\Cytech_Underground_Map\Cytech_Underground_Terrain\source\Images\layers.cfg" "P:\cytech\Cytech_Aboveground_Map\Cytech_Aboveground_Terrain\Source\Images\Mask\mask_aboveground.tif" -rgbv 10 10 10 -rgbt 0.5
}
]
}
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ In that way we can use the mask and the ground texture to generate a nice satmap
<img src="imgs\surfacefade_5.jpg" alt="drawing" style="max-width:50%; text-align: center;"/>
<img src="imgs\surfacefade_6.jpg" alt="drawing" style="max-width:50%; text-align: center;"/>
<img src="imgs\surfacefade_7.jpg" alt="drawing" style="max-width:50%; text-align: center;"/>

<img src="imgs\noise_generation_1.png" alt="drawing" style="max-width:80%; text-align: center;"/>
</details>

Surface and Satmap texture blends pretty good
Expand All @@ -35,26 +37,30 @@ Surface and Satmap texture blends pretty good
# Usage

```sh
python maskToSatMap.py [layers] [mask] [-o, --output OUTPUT] [-wd, --workdrive WORKDRIVE] [-cwd, --working_directory DIRECTORY] [-D, --Debug]
python maskToSatMap.py [layers] [mask] [-o, --output OUTPUT] [-wd, --workdrive WORKDRIVE] [-rgbv R_VARIATION G_VARIATION B_VARIATION] [-nc NOISECOVERAGE] [-D, --Debug]
- OR -
maskToSatMap.exe [layers] [mask] [-o, --output OUTPUT] [-wd, --workdrive WORKDRIVE] [-cwd, --working_directory DIRECTORY] [-D, --Debug]
maskToSatMap.exe [layers] [mask] [-o, --output OUTPUT] [-wd, --workdrive WORKDRIVE] [-rgbv R_VARIATION G_VARIATION B_VARIATION] [-nc NOISECOVERAGE] [-D, --Debug]
```

| Parameter | Function | Default |
| ---- | ----- | ---- |
| layers | path to layers.cfg file | |
| mask | path to terrain mask image file | |
| -o, --output | path and filename of output file | ./sat_map.tiff |
| -wd, --workdrive | drive letter of Arma3 workdrive | P:\ |
| -cwd, --working_directory | set the working directory of the program<br/>paths will be relative to this directory | ./ |
| -D, --Debug | enables verbose output | |
| Parameter | Function | Default | |
| ---- | ----- | ---- | ---- |
| layers | path to layers.cfg file | | |
| mask | path to terrain mask image file | | |
| -o, --output | path and filename of output file | ./sat_map.tiff | |
| -wd, --workdrive | drive letter of Arma3 workdrive | P:\ | |
| -rgbv, --rgbvariation | set rgb variation | 0 0 0 | * |
| -nc, --noisecoverage | noise coverage fraction | 0.0 | * |
| -D, --Debug | enables verbose output | | |


\* can only be used in conjunction, be aware that even with the use of multithreading, the noise generation can take a lot of time depending on your map size. Roughly 30s for 8k\*8k, 15min for 30k\*30k.

Example:
```sh
maskToSatMap.exe "layers.cfg" "Mask\mask_underground.tiff"
```
```sh
maskToSatMap.exe "layers.cfg" "Mask\mask_underground.tiff" -o "Sat\sat_map.tiff" -cwd "P:\cytech\Cytech_Underground_Map\Cytech_Underground_Terrain\source\Images"
maskToSatMap.exe "layers.cfg" "Mask\mask_underground.tiff" -o "Sat\sat_map.tiff" -rgbv 5 5 5 -nc 0.90
```

Please note:
Expand All @@ -65,7 +71,7 @@ If the average value of any texture happens to be pink, then the program will in
# Build

```
pip install pyinstaller
pip install pyinstaller, numpy, numba, Pillow
pyinstaller maskToSatMap.py -F
```

Expand Down
Binary file added a3_SourceData/smal_test_mask.tiff
Binary file not shown.
Binary file modified dist/maskToSatMap.exe
Binary file not shown.
Binary file added imgs/noise_generation_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 56 additions & 18 deletions maskToSatMap.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
import re
import argparse
import sys
import time
from dataclasses import dataclass
import logging

from pathlib import Path
from typing import Dict
import numpy as np
import numba as nb
from PIL import Image
Image.MAX_IMAGE_PIXELS = None

Expand All @@ -40,7 +42,6 @@ class Surface:
mask_color: int = 0xFFFFFF
avg_color: tuple[int,int,int] = ERRORCOLOR


def get_mask_avg_col_map(surfaces: list[Surface]):
"""build colormap from loaded mask colors to loaded average texture colors
Expand All @@ -54,7 +55,6 @@ def get_mask_avg_col_map(surfaces: list[Surface]):
logging.debug(f"Mapping {surf.mask_color:06X} to {surf.avg_color}")
nmap[surf.mask_color] = surf.name
return col_map, nmap


def read_layers_cfg(path):
""" Reads a arma3 layers.cfg and graps mask color to suface material information
Expand Down Expand Up @@ -102,8 +102,9 @@ def read_layers_cfg(path):
logging.debug (surfaces)
return surfaces

def replace_mask_color(mask_path, surfaces: Dict[str, Surface], target_path):
def replace_mask_color(mask_path, surfaces: Dict[str, Surface]):
"""replaces the mask colors with the average colors of the corresponding texture as defined in layers.cfg"""

# load mask
try:
img = Image.open(mask_path).convert("RGB")
Expand All @@ -116,6 +117,7 @@ def replace_mask_color(mask_path, surfaces: Dict[str, Surface], target_path):
# convert rgb tuple into 32 bit int 0x00RRGGBB, by shifting and adding via dot product
logging.info("Processing mask")
mask_32 = mask.dot(np.array([0x10000, 0x100, 0x1], dtype=np.int32))

del mask

# get color map from loaded layers.cfg and contained textures average colors, maps int32 colors (index) to RGB tuples from paa files
Expand All @@ -141,11 +143,7 @@ def replace_mask_color(mask_path, surfaces: Dict[str, Surface], target_path):
if name_map:
logging.warning("Unused textures: " + ", ".join(name_map.values()))

# export
logging.info(f"Exporting sat map to {target_path}")
out = Image.fromarray(sat_map)
out.save(target_path)

return sat_map

def find_paa_path(rvmat_path):
"""Extracts the path of the paa file corresponding to the given rvmat file"""
Expand Down Expand Up @@ -193,15 +191,53 @@ def load_average_colors(surfaces: dict[str, Surface]):
logging.debug(surf)
return surfaces

def noise_generation(sat_map, rgb_variation, noise_coverage):
"""generates a noise for a given threshold and a given pixel variation range"""
# checking inputs
if noise_coverage == 0:
logging.info(f"Skipping noise generation - The rgb threshold was set to 0 or not given")
return sat_map
elif sum(rgb_variation) == 0:
logging.info(f"Skipping noise generation - The rgb variation was set to 0,0,0 or not given")
return sat_map

# rand = ((np.random.rand(*sat_map.shape) - 0.5) * rgb_variation * (np.random.random()<noise_coverage)).astype(np.int8) # uniform distribution
# rand = (np.random.randn(*sat_map.shape) * rgb_variation).astype(np.int8) # gaussian distribution
# replacing pixels
# sat_map = np.clip(sat_map + rand, a_min=0, a_max=255).astype(np.uint8)
sat_map = vec_noise(sat_map, np.array(rgb_variation).astype(np.float64), noise_coverage)
return sat_map

@nb.guvectorize(["void(uint8[:], float64[:], float64, uint8[:])"], "(n),(n),() -> (n)" ,target="parallel")
def vec_noise(rgb, variation, threshold, out):
if threshold > np.random.random():
rand = ((np.random.rand(3) - 0.5) * variation).astype(np.int8)
else:
rand = np.zeros(rgb.shape, dtype=np.int8)
# rr, rg, rb = rng.integers(rgb - variation, rgb + variation)
out[:] = np.clip(rgb + rand, a_min=0, a_max=255).astype(np.uint8)

def start(layers, mask, output):
def export_map(sat_map, target_path):
# export
logging.info(f"Exporting sat map to {target_path}")
Image.fromarray(sat_map).save(target_path)#, compression="lzma")

def start(layers, mask, output, rgb_variation, noise_coverage):
logging.info("Starting ...")
strt = time.time()
logging.info("Reading layers.cfg")
surfaces = read_layers_cfg(layers)
logging.info("Loading average colors from textures")
surfaces = load_average_colors(surfaces)
logging.info(f"\tElapsed {time.time() - strt:.2f} s")
logging.info("Starting sat map generation")
replace_mask_color(mask, surfaces, output)
sat_map = replace_mask_color(mask, surfaces)
logging.info(f"\tElapsed {time.time() - strt:.2f} s")
logging.info("Starting sat map noise generation")
sat_map = noise_generation(sat_map, rgb_variation, noise_coverage)
logging.info(f"\tElapsed {time.time() - strt:.2f} s")
logging.info("Saving sat map")
export_map(sat_map ,output)
logging.info("... Done")
return

Expand All @@ -219,23 +255,23 @@ def start(layers, mask, output):
parser.add_argument("mask", type=str, help="the terrain mask .tiff image file")
parser.add_argument("-wd", "--workdrive", type=str, default="P:\\", help="drive letter of the Arma3 tools work drive")
parser.add_argument("-o", "--output", type=str, default="./sat_img.tiff", help="path of the resulting sat view image file")
parser.add_argument("-rgbv", "--rgbvariation", type=int, default=0, nargs=3, help="slight variation of the average ground texture color in +/- color range")
parser.add_argument("-nc", "--noisecoverage", type=float, default=0.0, help="percentage of overall rgb variation")
parser.add_argument("-D","--Debug", action="store_true", help="increases verbosity")

args = parser.parse_args()

layers_path = Path(args.layers)
mask_path = Path(args.mask)
out_path = Path(args.output)


noise_coverage = args.noisecoverage
rgb_variation = args.rgbvariation
if rgb_variation == 0:
rgb_variation = [0,0,0]

assert layers_path.exists(), f"Layers file {args.layers} does not exist"
assert mask_path.exists(), f"Mask file {args.mask} does not exist"

# if (args.output != "./sat_img.tiff"):
# try:
# out_path.resolve(strict=True)
# except:
# assert False, f"Output file name {args.output} could not be resolved"


if args.Debug:
Expand All @@ -246,6 +282,8 @@ def start(layers, mask, output):
drv = Path(args.workdrive)
assert drv.exists(), "invalid workdrive"
WORKDRIVE = drv



start(layers_path, mask_path, out_path)
start(layers_path, mask_path, out_path, rgb_variation, noise_coverage)

0 comments on commit a7867cc

Please sign in to comment.