More region classification

conquest_develop
bobloy 5 years ago
parent b46a5920f7
commit c3c40c594e

@ -6,14 +6,14 @@ from shutil import copyfile
from typing import Optional, Union from typing import Optional, Union
import discord import discord
from PIL import Image, ImageChops, ImageColor, ImageOps from PIL import Image, ImageColor, ImageOps
from discord.ext.commands import Greedy from discord.ext.commands import Greedy
from redbot.core import Config, commands from redbot.core import Config, commands
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.data_manager import bundled_data_path, cog_data_path from redbot.core.data_manager import bundled_data_path, cog_data_path
from redbot.core.utils.predicates import MessagePredicate from redbot.core.utils.predicates import MessagePredicate
from conquest.regioner import ConquestMap, Regioner, get_center from conquest.regioner import ConquestMap, composite_regions
class Conquest(commands.Cog): class Conquest(commands.Cog):
@ -31,6 +31,10 @@ class Conquest(commands.Cog):
"custom": True, "custom": True,
} }
default_maps_json = {
"maps": []
}
def __init__(self, bot: Red): def __init__(self, bot: Red):
super().__init__() super().__init__()
self.bot = bot self.bot = bot
@ -49,7 +53,7 @@ class Conquest(commands.Cog):
if not self.custom_map_path.exists() or not self.custom_map_path.is_dir(): if not self.custom_map_path.exists() or not self.custom_map_path.is_dir():
self.custom_map_path.mkdir() self.custom_map_path.mkdir()
with (self.custom_map_path / "maps.json").open("w+") as dj: with (self.custom_map_path / "maps.json").open("w+") as dj:
json.dump({"maps": []}, dj, sort_keys=True, indent=4) json.dump(self.default_maps_json.copy(), dj, sort_keys=True, indent=4)
self.current_map_folder = self.data_path / "current_maps" self.current_map_folder = self.data_path / "current_maps"
if not self.current_map_folder.exists() or not self.current_map_folder.is_dir(): if not self.current_map_folder.exists() or not self.current_map_folder.is_dir():
@ -64,7 +68,6 @@ class Conquest(commands.Cog):
self.ext_format = None self.ext_format = None
self.mm: Union[ConquestMap, None] = None self.mm: Union[ConquestMap, None] = None
# self.mm_img: Union[Image.Image, None] = None
async def red_delete_data_for_user(self, **kwargs): async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete""" """Nothing to delete"""
@ -90,8 +93,13 @@ class Conquest(commands.Cog):
async def current_map_load(self): async def current_map_load(self):
map_path = self._path_if_custom() map_path = self._path_if_custom()
map_data_path = map_path / self.current_map / "data.json" map_data_path = map_path / self.current_map / "data.json"
try:
with map_data_path.open() as mapdata: with map_data_path.open() as mapdata:
self.map_data: dict = json.load(mapdata) self.map_data: dict = json.load(mapdata)
except FileNotFoundError as e:
print(e)
await self.config.current_map.set(None)
return
self.ext = self.map_data["extension"] self.ext = self.map_data["extension"]
self.ext_format = "JPEG" if self.ext.upper() == "JPG" else self.ext.upper() self.ext_format = "JPEG" if self.ext.upper() == "JPG" else self.ext.upper()
@ -127,46 +135,14 @@ class Conquest(commands.Cog):
current_img_path = await self._get_current_map_path() / f"current.{self.ext}" current_img_path = await self._get_current_map_path() / f"current.{self.ext}"
im = Image.open(current_img_path) im = Image.open(current_img_path)
async with ctx.typing(): async with ctx.typing():
out: Image.Image = await self._composite_regions(im, regions, color, out: Image.Image = await composite_regions(
self._path_if_custom() / self.current_map) im, regions, color, self._path_if_custom() / self.current_map
)
out.save(current_img_path, self.ext_format) out.save(current_img_path, self.ext_format)
await self._send_maybe_zoomed_map(ctx, current_img_path, f"map.{self.ext}") await self._send_maybe_zoomed_map(ctx, current_img_path, f"map.{self.ext}")
async def _composite_regions(self, im, regions, color, region_path) -> Union[Image.Image, None]: async def _mm_save_map(self, map_name, target_save):
im2 = Image.new("RGB", im.size, color) return await self.mm.change_name(map_name, target_save)
loop = asyncio.get_running_loop()
combined_mask = None
for region in regions:
mask = Image.open(
region_path / "masks" / f"{region}.{self.ext}"
).convert("1")
if combined_mask is None:
combined_mask = mask
else:
# combined_mask = ImageChops.logical_or(combined_mask, mask)
combined_mask = await loop.run_in_executor(
None, ImageChops.logical_and, combined_mask, mask
)
if combined_mask is None: # No regions usually
return None
out = await loop.run_in_executor(None, Image.composite, im, im2, combined_mask.convert("L"))
return out
async def _mm_save_map(self, ctx, map_name, target_save):
result = await self.mm.change_name(map_name, target_save)
if result:
await ctx.maybe_send_embed("Name changed")
async def _save_mm_data(self, target_save):
data_json = target_save / "data.json"
with data_json.open("w+") as dj:
json.dump(self.mm, dj, sort_keys=True, indent=4)
@commands.group() @commands.group()
async def mapmaker(self, ctx: commands.context): async def mapmaker(self, ctx: commands.context):
@ -180,7 +156,6 @@ class Conquest(commands.Cog):
async def _mapmaker_close(self, ctx: commands.Context): async def _mapmaker_close(self, ctx: commands.Context):
"""Close the currently open map.""" """Close the currently open map."""
self.mm = None self.mm = None
self.mm_img = None
await ctx.tick() await ctx.tick()
@ -191,17 +166,13 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed("No map currently being worked on") await ctx.maybe_send_embed("No map currently being worked on")
return return
if not self.mm_img: if self.mm.name == map_name:
await ctx.maybe_send_embed("No map image to save")
return
if self.mm["name"] == map_name:
await ctx.maybe_send_embed("This map already has that name, no reason to save") await ctx.maybe_send_embed("This map already has that name, no reason to save")
return return
target_save = self.custom_map_path / map_name target_save = self.custom_map_path / map_name
result = await self._mm_save_map(ctx, map_name, target_save) result = await self._mm_save_map(map_name, target_save)
if not result: if not result:
await ctx.maybe_send_embed("Failed to save to that name") await ctx.maybe_send_embed("Failed to save to that name")
else: else:
@ -233,6 +204,7 @@ class Conquest(commands.Cog):
if not self.mm: if not self.mm:
self.mm = ConquestMap(self.custom_map_path) self.mm = ConquestMap(self.custom_map_path)
self.mm.custom = True
if map_path: if map_path:
map_path = pathlib.Path(map_path) map_path = pathlib.Path(map_path)
@ -241,8 +213,7 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed("Map not found at that path") await ctx.maybe_send_embed("Map not found at that path")
return return
self.mm_img = Image.open(map_path) mm_img = Image.open(map_path)
self.mm.extension = map_path.suffix[1:]
elif message.attachments: elif message.attachments:
attch: discord.Attachment = message.attachments[0] attch: discord.Attachment = message.attachments[0]
@ -251,20 +222,25 @@ class Conquest(commands.Cog):
buffer = BytesIO() buffer = BytesIO()
await attch.save(buffer) await attch.save(buffer)
self.mm_img: Image.Image = Image.open(buffer) mm_img: Image.Image = Image.open(buffer)
self.mm["extension"] = pathlib.Path(attch.filename).suffix[1:]
else: else:
# Wait what? # Wait what?
return return
result = await self._mm_save_map(ctx, map_name, target_save) result = await self.mm.init_directory(map_name, target_save, mm_img)
if not result: if not result:
self.mm = None self.mm = None
self.mm_img = None
await ctx.maybe_send_embed("Failed to upload to that name") await ctx.maybe_send_embed("Failed to upload to that name")
else: return
maps_json_path = self.custom_map_path / "maps.json"
with maps_json_path.open("r+") as maps:
map_data = json.load(maps)
map_data["maps"].append(map_name)
maps.seek(0)
json.dump(map_data, maps, sort_keys=True, indent=4)
await ctx.maybe_send_embed(f"Map successfully uploaded to {target_save}") await ctx.maybe_send_embed(f"Map successfully uploaded to {target_save}")
@mapmaker.command(name="sample") @mapmaker.command(name="sample")
@ -274,65 +250,20 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed("No map currently being worked on") await ctx.maybe_send_embed("No map currently being worked on")
return return
if not self.mm_img:
await ctx.maybe_send_embed("No map image has been loaded")
return
async with ctx.typing(): async with ctx.typing():
map_dir = self.custom_map_path / self.mm["name"]
files = []
file1 = discord.File(map_dir / f"blank.{self.mm['extension']}") files = await self.mm.get_sample()
files.append(file1)
masks_dir = map_dir / "masks"
if masks_dir.exists() and masks_dir.is_dir():
loop = asyncio.get_running_loop()
current_map = Image.open(map_dir / f"blank.{self.mm['extension']}")
regions = list(self.mm["regions"].keys())
fourth = len(regions) // 4
current_map = await self._composite_regions(
current_map, regions[:fourth], ImageColor.getrgb("red"), map_dir
)
current_map = await self._composite_regions(
current_map, regions[fourth: fourth * 2], ImageColor.getrgb("green"), map_dir
)
current_map = await self._composite_regions(
current_map, regions[fourth * 2: fourth * 3], ImageColor.getrgb("blue"), map_dir
)
current_map = await self._composite_regions(
current_map, regions[fourth * 3:], ImageColor.getrgb("yellow"), map_dir
)
numbers = Image.open(map_dir / "numbers.png").convert("L")
inverted_map = ImageOps.invert(current_map)
current_numbered_img = await loop.run_in_executor(
None, Image.composite, current_map, inverted_map, numbers
)
buffer1 = BytesIO()
buffer2 = BytesIO()
current_map.save(buffer1, "png")
buffer1.seek(0)
current_numbered_img.save(buffer2, "png")
buffer2.seek(0)
files.append(discord.File(fp=buffer1, filename="colored_map.png"))
files.append(discord.File(fp=buffer2, filename="with_numbers.png"))
for f in files: for f in files:
await ctx.send(file=f) await ctx.send(file=discord.File(f))
@mapmaker.command(name="load") @mapmaker.command(name="load")
async def _mapmaker_load(self, ctx: commands.Context, map_name: str): async def _mapmaker_load(self, ctx: commands.Context, map_name: str):
"""Load an existing map to be modified.""" """Load an existing map to be modified."""
if self.mm or self.mm_img: if self.mm:
await ctx.maybe_send_embed("There is a current map in progres. Close it first with `[p]mapmaker close`") await ctx.maybe_send_embed(
"There is a current map in progress. Close it first with `[p]mapmaker close`"
)
return return
map_path = self.custom_map_path / map_name map_path = self.custom_map_path / map_name
@ -341,12 +272,8 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed(f"Map {map_name} not found in {self.custom_map_path}") await ctx.maybe_send_embed(f"Map {map_name} not found in {self.custom_map_path}")
return return
maps_json = map_path / "data.json" self.mm = ConquestMap(map_path)
await self.mm.load_data()
with maps_json.open() as maps:
self.mm = json.load(maps)
self.mm_img = Image.open(map_path / f"blank.{self.mm['extension']}")
await ctx.tick() await ctx.tick()
@ -368,34 +295,18 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed("No map currently being worked on") await ctx.maybe_send_embed("No map currently being worked on")
return return
if not self.mm_img: masks_dir = self.mm.masks_path()
await ctx.maybe_send_embed("No map image to save")
return
map_dir = self.custom_map_path / self.mm["name"]
masks_dir = map_dir / "masks"
if masks_dir.exists() and masks_dir.is_dir(): if masks_dir.exists() and masks_dir.is_dir():
await ctx.maybe_send_embed("Mask folder already exists, delete this before continuing") await ctx.maybe_send_embed("Mask folder already exists, delete this before continuing")
return return
# Done by Regioner
# masks_dir.mkdir()
regioner = Regioner(filename=f"blank.{self.mm['extension']}", filepath=map_dir)
loop = asyncio.get_running_loop()
with ctx.typing(): with ctx.typing():
regions = await loop.run_in_executor(None, regioner.execute) regions = await self.mm.generate_masks()
if not regions: if not regions:
await ctx.maybe_send_embed("Failed to generate masks") await ctx.maybe_send_embed("Failed to generate masks")
return return
self.mm["regions"] = regions
self.mm["region_max"] = len(regions) + 1
await self._save_mm_data(map_dir)
await ctx.maybe_send_embed(f"{len(regions)} masks generated into {masks_dir}") await ctx.maybe_send_embed(f"{len(regions)} masks generated into {masks_dir}")
@_mapmaker_masks.command(name="combine") @_mapmaker_masks.command(name="combine")
@ -407,24 +318,19 @@ class Conquest(commands.Cog):
await ctx.maybe_send_embed("No map currently being worked on") await ctx.maybe_send_embed("No map currently being worked on")
return return
if not self.mm_img:
await ctx.maybe_send_embed("No map image to save")
return
if recommended and mask_list: if recommended and mask_list:
await ctx.maybe_send_embed( await ctx.maybe_send_embed(
"Can't combine recommend masks and a mask list at the same time, pick one" "Can't combine recommend masks and a mask list at the same time, pick one"
) )
return return
map_dir = self.custom_map_path / self.mm["name"] masks_dir = self.mm.masks_path()
masks_dir = map_dir / "masks"
if not masks_dir.exists() or not masks_dir.is_dir(): if not masks_dir.exists() or not masks_dir.is_dir():
await ctx.maybe_send_embed("There are no masks") await ctx.maybe_send_embed("There are no masks")
return return
if not recommended: if not recommended:
for mask in mask_list: for mask in mask_list: # TODO: switch to self.mm.regions intersection of sets
m = masks_dir / f"{mask}.png" m = masks_dir / f"{mask}.png"
if not m.exists(): if not m.exists():
await ctx.maybe_send_embed(f"Mask #{mask} does not exist") await ctx.maybe_send_embed(f"Mask #{mask} does not exist")
@ -433,29 +339,13 @@ class Conquest(commands.Cog):
await ctx.send("Not Implemented") await ctx.send("Not Implemented")
return return
regioner = Regioner(filename=f"blank.{self.mm['extension']}", filepath=map_dir) result = await self.mm.combine_masks(mask_list)
if not result:
loop = asyncio.get_running_loop()
lowest, eliminated = await loop.run_in_executor(None, regioner.combine_masks, mask_list)
if not lowest:
await ctx.maybe_send_embed("Failed to combine masks")
return
points = [self.mm["regions"][f"{n}"]["center"] for n in mask_list]
self.mm["regions"][f"{lowest}"]["center"] = get_center(points)
for key in eliminated:
self.mm["regions"].pop(f"{key}")
future = await loop.run_in_executor(None, regioner.create_number_mask, self.mm["regions"])
if not future:
await ctx.maybe_send_embed( await ctx.maybe_send_embed(
"Failed to generate number mask, try running this command again" "Failed to combine masks, try the command again or check log for errors"
) )
return
await self._save_mm_data(map_dir) await ctx.tick()
@commands.group() @commands.group()
async def conquest(self, ctx: commands.Context): async def conquest(self, ctx: commands.Context):

@ -1,11 +1,38 @@
import asyncio
import json import json
import pathlib import pathlib
from typing import List import shutil
from io import BytesIO
from typing import List, Union
from PIL import Image, ImageChops, ImageColor, ImageDraw, ImageFont from PIL import Image, ImageChops, ImageColor, ImageDraw, ImageFont, ImageOps
from PIL.ImageDraw import _color_diff from PIL.ImageDraw import _color_diff
async def composite_regions(im, regions, color, masks_path) -> Union[Image.Image, None]:
im2 = Image.new("RGB", im.size, color)
loop = asyncio.get_running_loop()
combined_mask = None
for region in regions:
mask = Image.open(masks_path / f"{region}.png").convert("1")
if combined_mask is None:
combined_mask = mask
else:
# combined_mask = ImageChops.logical_or(combined_mask, mask)
combined_mask = await loop.run_in_executor(
None, ImageChops.logical_and, combined_mask, mask
)
if combined_mask is None: # No regions usually
return None
out = await loop.run_in_executor(None, Image.composite, im, im2, combined_mask.convert("L"))
return out
def get_center(points): def get_center(points):
""" """
Taken from https://stackoverflow.com/questions/4355894/how-to-get-center-of-set-of-points-using-python Taken from https://stackoverflow.com/questions/4355894/how-to-get-center-of-set-of-points-using-python
@ -81,17 +108,15 @@ def floodfill(image, xy, value, border=None, thresh=0) -> set:
class ConquestMap: class ConquestMap:
def __init__(self, path): def __init__(self, path: pathlib.Path):
self.path = path self.path = path
self.name = None self.name = None
self.custom = None self.custom = None
self.region_max = None self.region_max = None
self.extension = None
self.regions = {} self.regions = {}
async def change_name(self, new_name: str, new_path: pathlib.Path): async def change_name(self, new_name: str, new_path: pathlib.Path):
self.name = new_name
if new_path.exists() and new_path.is_dir(): if new_path.exists() and new_path.is_dir():
# This is an overwrite operation # This is an overwrite operation
# await ctx.maybe_send_embed(f"{map_name} already exists, okay to overwrite?") # await ctx.maybe_send_embed(f"{map_name} already exists, okay to overwrite?")
@ -108,10 +133,13 @@ class ConquestMap:
# This is a new name # This is a new name
new_path.mkdir() new_path.mkdir()
ext_format = "JPEG" if self.extension.upper() == "JPG" else self.extension.upper()
self.mm_img.save(new_path / f"blank.{self.extension}", ext_format)
await self._save_mm_data(target_save) shutil.copytree(self.path, new_path)
self.name = new_name
self.path = new_path
await self.save_data()
return True return True
@ -122,7 +150,7 @@ class ConquestMap:
return self.path / "data.json" return self.path / "data.json"
def blank_path(self): def blank_path(self):
return self.path / "blank.png" return self.path / "blank.png" # Everything is png now
def numbers_path(self): def numbers_path(self):
return self.path / "numbers.png" return self.path / "numbers.png"
@ -130,11 +158,26 @@ class ConquestMap:
def numbered_path(self): def numbered_path(self):
return self.path / "numbered.png" return self.path / "numbered.png"
def save_data(self): async def init_directory(self, name: str, path: pathlib.Path, image: Image.Image):
if not path.exists() or not path.is_dir():
path.mkdir()
self.name = name
self.path = path
await self.save_data()
image.save(self.blank_path(), "PNG")
return True
async def save_data(self):
to_save = self.__dict__.copy()
to_save.pop("path")
with self.data_path().open("w+") as dp: with self.data_path().open("w+") as dp:
json.dump(self.__dict__, dp, sort_keys=True, indent=4) json.dump(to_save, dp, sort_keys=True, indent=4)
def load_data(self): async def load_data(self):
with self.data_path().open() as dp: with self.data_path().open() as dp:
data = json.load(dp) data = json.load(dp)
@ -142,24 +185,144 @@ class ConquestMap:
self.custom = data["custom"] self.custom = data["custom"]
self.region_max = data["region_max"] self.region_max = data["region_max"]
self.regions = {key: Region(number=key, host=self, **data) for key, data in data["regions"].items()} self.regions = {key: Region(**data) for key, data in data["regions"].items()}
def save_region(self, region): async def save_region(self, region):
if not self.custom: if not self.custom:
return False return False
pass pass
async def generate_masks(self):
regioner = Regioner(filename="blank.png", filepath=self.path)
loop = asyncio.get_running_loop()
regions = await loop.run_in_executor(None, regioner.execute)
if not regions:
return regions
self.regions = regions
self.region_max = len(regions) + 1
await self.save_data()
async def create_number_mask(self):
regioner = Regioner(filename="blank.png", filepath=self.path)
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, regioner.create_number_mask, self.regions)
async def combine_masks(self, mask_list: List[int]):
loop = asyncio.get_running_loop()
lowest, eliminated = await loop.run_in_executor(None, self._img_combine_masks, mask_list)
if not lowest:
return lowest
elim_regions = [self.regions[n] for n in eliminated]
lowest_region = self.regions[lowest]
# points = [self.mm["regions"][f"{n}"]["center"] for n in mask_list]
#
# points = [(r.center, r.weight) for r in elim_regions]
weighted_points = [r.center for r in elim_regions for _ in range(r.weight)]
lowest_region.center = get_center(weighted_points)
for key in eliminated:
self.regions.pop(key)
# self.mm["regions"].pop(f"{key}")
if self.region_max in eliminated: # Max region has changed
self.region_max = max(self.regions.keys())
await self.create_number_mask()
await self.save_data()
def _img_combine_masks(self, mask_list: List[int]):
if not mask_list:
return False, None
if not self.blank_path().exists():
return False, None
if not self.masks_path().exists():
return False, None
base_img: Image.Image = Image.open(self.blank_path())
mask = Image.new("1", base_img.size, 1)
lowest_num = None
eliminated_masks = []
for mask_num in mask_list:
if lowest_num is None or mask_num < lowest_num:
lowest_num = mask_num
else:
eliminated_masks.append(mask_num)
mask2 = Image.open(self.masks_path() / f"{mask_num}.png").convert("1")
mask = ImageChops.logical_and(mask, mask2)
mask.save(self.masks_path() / f"{lowest_num}.png", "PNG")
return lowest_num, eliminated_masks
async def get_sample(self):
files = [self.blank_path()]
masks_dir = self.masks_path()
if masks_dir.exists() and masks_dir.is_dir():
loop = asyncio.get_running_loop()
current_map = Image.open(self.blank_path())
regions = list(self.regions.keys())
fourth = len(regions) // 4
current_map = await composite_regions(
current_map, regions[:fourth], ImageColor.getrgb("red"), self.masks_path()
)
current_map = await composite_regions(
current_map,
regions[fourth: fourth * 2],
ImageColor.getrgb("green"),
self.masks_path(),
)
current_map = await composite_regions(
current_map,
regions[fourth * 2: fourth * 3],
ImageColor.getrgb("blue"),
self.masks_path(),
)
current_map = await composite_regions(
current_map, regions[fourth * 3:], ImageColor.getrgb("yellow"), self.masks_path()
)
numbers = Image.open(self.numbers_path()).convert("L")
inverted_map = ImageOps.invert(current_map)
current_numbered_img = await loop.run_in_executor(
None, Image.composite, current_map, inverted_map, numbers
)
buffer1 = BytesIO()
buffer2 = BytesIO()
current_map.save(buffer1, "png")
buffer1.seek(0)
current_numbered_img.save(buffer2, "png")
buffer2.seek(0)
files.append(buffer1)
files.append(buffer2)
return files
class Region: class Region:
def __init__(self, number, host: ConquestMap, center, **kwargs): def __init__(self, center, weight, **kwargs):
self.number = number
self.host = host
self.center = center self.center = center
self.weight = weight
self.data = kwargs self.data = kwargs
def save(self):
self.host.save_region(self)
class Regioner: class Regioner:
def __init__( def __init__(
@ -196,7 +359,7 @@ class Regioner:
already_processed = set() already_processed = set()
mask_count = 0 mask_count = 0
mask_centers = {} regions = {}
for y1 in range(base_img.height): for y1 in range(base_img.height):
for x1 in range(base_img.width): for x1 in range(base_img.width):
@ -213,16 +376,18 @@ class Regioner:
mask = mask.convert("L") mask = mask.convert("L")
mask.save(masks_path / f"{mask_count}.png", "PNG") mask.save(masks_path / f"{mask_count}.png", "PNG")
mask_centers[mask_count] = {"center": get_center(filled), "point_count": len(filled)} regions[mask_count] = Region(
center=get_center(filled), weight=len(filled)
)
already_processed.update(filled) already_processed.update(filled)
# TODO: save mask_centers # TODO: save mask_centers
self.create_number_mask(mask_centers) self.create_number_mask(regions)
return mask_centers return regions
def create_number_mask(self, mask_centers): def create_number_mask(self, regions):
base_img_path = self.filepath / self.filename base_img_path = self.filepath / self.filename
if not base_img_path.exists(): if not base_img_path.exists():
return False return False
@ -232,39 +397,9 @@ class Regioner:
number_img = Image.new("L", base_img.size, 255) number_img = Image.new("L", base_img.size, 255)
fnt = ImageFont.load_default() fnt = ImageFont.load_default()
d = ImageDraw.Draw(number_img) d = ImageDraw.Draw(number_img)
for mask_num, data in mask_centers.items(): for region_num, region in regions.items():
center = data["center"] center = region.center
d.text(center, str(mask_num), font=fnt, fill=0) text = getattr(region, "center", str(region_num))
d.text(center, text, font=fnt, fill=0)
number_img.save(self.filepath / f"numbers.png", "PNG") number_img.save(self.filepath / f"numbers.png", "PNG")
return True return True
def combine_masks(self, mask_list: List[int]):
if not mask_list:
return False, None
base_img_path = self.filepath / self.filename
if not base_img_path.exists():
return False, None
masks_path = self.filepath / "masks"
if not masks_path.exists():
return False, None
base_img: Image.Image = Image.open(base_img_path)
mask = Image.new("1", base_img.size, 1)
lowest_num = None
eliminated_masks = []
for mask_num in mask_list:
if lowest_num is None or mask_num < lowest_num:
lowest_num = mask_num
else:
eliminated_masks.append(mask_num)
mask2 = Image.open(masks_path / f"{mask_num}.png").convert("1")
mask = ImageChops.logical_and(mask, mask2)
mask.save(masks_path / f"{lowest_num}.png", "PNG")
return lowest_num, eliminated_masks

Loading…
Cancel
Save