import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.colors as mcolors
from collections import defaultdict
import os
import imageio
import numpy as np
from IPython.display import Image
[docs]def plot_organisms(step, organisms, grid_size=20, output_dir="frames", foodweb=None, terrain=None):
"""
Plots the state of the simulation at a given step.
This function visualizes the environment and all organisms present at the specified step.
It overlays terrain types and animal positions using colored markers. If multiple organisms
are at the same position, they are slightly offset to be visible. Dead organisms are marked with 'x'.
Parameters:
step (int): The simulation step number.
organisms (list): List of organism objects with species, coordinates, and alive status.
grid_size (int): Size of the simulation grid (default is 20).
output_dir (str): Directory to save the output PNG (default is "frames").
foodweb (object): Optional foodweb object to map species to consistent colors.
terrain (object): Optional terrain object to render environmental backgrounds.
"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
scale = 0.3 # 1 rácsmezőhöz ennyi inch jusson
figsize = (grid_size * scale, grid_size * scale)
plt.figure(figsize=figsize)
ax = plt.gca()
ax.set_facecolor("#a1cca3") # halványzöld
ax.grid(True, color="#a1cca3") # rácsvonalak halványzöld
ax.set_xlim(0, grid_size)
ax.set_ylim(0, grid_size)
ax.set_xticks(range(0, grid_size + 1))
ax.set_yticks(range(0, grid_size + 1))
ax.grid(True)
# Háttérszínek terrain típusokhoz
terrain_colors = {
"tree": "#2e8b57", # sötétzöld
"water": "#add8e6", # világoskék
"hill": "#7d705c", # barnás
"shelter": "#d3d3d3", # világosszürke
"plain": "#a1cca3" # halványzöld
}
# Terep kirajzolása háttérként
if terrain:
for x in range(grid_size):
for y in range(grid_size):
ttype = terrain.get_type(x, y)
rect = plt.Rectangle((x, y), 1, 1, color=terrain_colors.get(ttype, "#ffffff"), zorder=0)
ax.add_patch(rect)
# Színek fajonként
color_map = get_species_colors(organisms, foodweb=foodweb)
position_counts = defaultdict(int)
# Élőlények kirajzolása
for org in organisms:
pos = (org.x, org.y)
i = position_counts[pos]
position_counts[pos] += 1
offset = 0.1 * i
if not org.alive:
marker = "x"
color = "gray"
else:
marker = "o"
color = color_map.get(org.species, "black")
ax.plot(org.x + 0.5 + offset, org.y + 0.5 + offset, marker, color=color, markersize=10, zorder=1)
plt.title(f"Step {step}")
plt.savefig(f"{output_dir}/step_{step:03d}.png")
plt.close()
[docs]def get_species_colors(organisms, foodweb=None):
"""
Assigns consistent colors to each species based on the foodweb or automatically.
If a foodweb object is provided, species colors are fetched using its color mapping.
Otherwise, a default colormap ('tab10') is used to assign distinguishable colors.
Parameters:
organisms (list): List of organism objects containing a .species attribute.
foodweb (object, optional): Object with a get_color(species) method.
Returns:
dict[str, str]: Mapping from species name to hex color code.
"""
if foodweb:
return {org.species: foodweb.get_color(org.species) for org in organisms}
else:
species = list({org.species for org in organisms})
cmap = cm.get_cmap('tab10', len(species))
return {sp: mcolors.to_hex(cmap(i)) for i, sp in enumerate(species)}
[docs]def create_animation(frame_folder: str = "frames", output_path: str = "statistics_plots/animation.gif", fps: int = 4, frame_range: tuple[int, int] = None) -> None:
"""
Creates an animated GIF from PNG frame images in the given folder.
Parameters:
frame_folder: Directory containing .png frames
output_path: Where to save the animation
fps: Frames per second
frame_range: Tuple (start, end) to limit frames, or None for all
"""
images = []
frame_files = sorted([f for f in os.listdir(frame_folder) if f.endswith(".png")])
if frame_range:
start, end = frame_range
frame_files = frame_files[start:end]
for filename in frame_files:
img_path = os.path.join(frame_folder, filename)
images.append(imageio.imread(img_path))
if not images:
print("No frames found to create animation.")
return
os.makedirs(os.path.dirname(output_path), exist_ok=True)
imageio.mimsave(output_path, images, fps=fps)
print(f"✅ Animation saved to {output_path}")
return Image(output_path)
[docs]def compose_frames_side_by_side(frame_indices: list[int], frame_folder: str = "frames", output_path: str = "statistics_plots/frames_side_by_side.png") -> None:
"""
Composes selected PNG frames side-by-side into a single image.
Parameters:
frame_indices: List of frame numbers (e.g. [0, 5, 10])
frame_folder: Directory containing frame PNGs
output_path: Output PNG file path
"""
images = []
for i in frame_indices:
filename = f"step_{i:03d}.png"
path = os.path.join(frame_folder, filename)
if os.path.exists(path):
images.append(imageio.imread(path))
else:
print(f"⚠️ Frame not found: {filename}")
if not images:
print("No valid frames to compose.")
return
combined_image = np.hstack(images)
os.makedirs(os.path.dirname(output_path), exist_ok=True)
imageio.imwrite(output_path, combined_image)
print(f"✅ Composed image saved to {output_path}")
return Image(output_path)