jeffrey1963/Cattle-Elk-R5
0
1cluster_to_class = {
2 0: "desert",
3 1: "pasture",
4 2: "riaprain",
5 3: "sensitive riparian",
6 4: "wetland",
7 5: "water"
8}
9
10
11import numpy as np
12from collections import deque
13
14def identify_zones(parcel_map, connectivity="queen"):
15 """
16 Identifies contiguous zones in a 2D parcel map using connected component labeling.
17
18 Parameters:
19 parcel_map (np.ndarray): 2D array where each value is a cluster ID (e.g., land type).
20 connectivity (str): "rook" (4-way) or "queen" (8-way) connectivity.
21
22 Returns:
23 zone_map (np.ndarray): Same shape as parcel_map, each zone gets a unique integer ID.
24 zone_to_cluster (dict): Maps each zone ID to its underlying cluster ID.
25 """
26 n_rows, n_cols = parcel_map.shape
27 zone_map = -1 * np.ones_like(parcel_map, dtype=int)
28 visited = np.zeros_like(parcel_map, dtype=bool)
29 zone_id = 0
30
31 if connectivity == "queen":
32 directions = [(-1, -1), (-1, 0), (-1, 1),
33 (0, -1), (0, 1),
34 (1, -1), (1, 0), (1, 1)]
35 else: # "rook"
36 directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
37
38 for i in range(n_rows):
39 for j in range(n_cols):
40 if visited[i, j]:
41 continue
42 cluster_id = parcel_map[i, j]
43 queue = deque([(i, j)])
44 while queue:
45 x, y = queue.popleft()
46 if visited[x, y] or parcel_map[x, y] != cluster_id:
47 continue
48 visited[x, y] = True
49 zone_map[x, y] = zone_id
50 for dx, dy in directions:
51 nx, ny = x + dx, y + dy
52 if 0 <= nx < n_rows and 0 <= ny < n_cols and not visited[nx, ny]:
53 if parcel_map[nx, ny] == cluster_id:
54 queue.append((nx, ny))
55 zone_id += 1
56
57 # Optional: Map zone_id → cluster_id
58 zone_to_cluster = {}
59 for zid in range(zone_id):
60 indices = np.argwhere(zone_map == zid)
61 if len(indices) > 0:
62 i, j = indices[0]
63 zone_to_cluster[zid] = parcel_map[i, j]
64
65 return zone_map, zone_to_cluster
66
67import matplotlib.pyplot as plt
68import numpy as np
69
70def plot_labeled_zones(zone_map, zone_labels, zone_to_cluster, save_path="labeled_zones.png"):
71 """
72 Plots a zone map with human-readable labels and cluster-based custom colors.
73
74 Colors (by cluster ID):
75 0: tan
76 1: green
77 2: rose
78 3: red
79 4: purple
80 5: blue
81 """
82 # Custom color map for cluster IDs (NOT zone IDs)
83 cluster_colors = {
84 0: "#D2B48C", # tan
85 1: "#228B22", # green
86 2: "#FF66CC", # rose
87 3: "#FF0000", # red
88 4: "#800080", # purple
89 5: "#1E90FF", # blue
90 }
91
92 n_rows, n_cols = zone_map.shape
93 rgb_map = np.zeros((n_rows, n_cols, 3))
94
95 # Map each parcel to its cluster color
96 for i in range(n_rows):
97 for j in range(n_cols):
98 zone_id = zone_map[i, j]
99 cluster_id = zone_to_cluster.get(zone_id, 0)
100 hex_color = cluster_colors.get(cluster_id, "#AAAAAA") # fallback = gray
101 rgb = tuple(int(hex_color.lstrip('#')[k:k+2], 16)/255 for k in (0, 2, 4))
102 rgb_map[i, j] = rgb
103
104 fig, ax = plt.subplots(figsize=(8, 6))
105 ax.imshow(rgb_map)
106
107 for zone_id, label in zone_labels.items():
108 positions = np.argwhere(zone_map == zone_id)
109 if len(positions) == 0:
110 continue
111 center_i, center_j = positions.mean(axis=0)
112 cluster = zone_to_cluster.get(zone_id, "?")
113 label_text = f"{label} ({cluster})"
114 ax.text(center_j, center_i, label_text, color="white", ha="center", va="center",
115 fontsize=9, fontweight="bold",
116 bbox=dict(facecolor="black", alpha=0.5, boxstyle="round,pad=0.3"))
117
118 ax.set_title("Labeled Grazing & Riparian Zones (Custom Colors)")
119 ax.axis('off')
120 plt.tight_layout()
121 plt.savefig(save_path)
122 plt.close(fig)
123
124
125import matplotlib.pyplot as plt
126import numpy as np
127
128def plot_labeled_zonesold3am(zone_map, zone_labels, zone_to_cluster, save_path="labeled_zones.png"):
129 """
130 Plots a zone map with fixed colors based on cluster class (e.g., pasture = green, desert = tan),
131 and appends the cluster ID to each label (e.g., "A (1)").
132
133 Parameters:
134 zone_map (np.ndarray): 2D array of zone IDs (integers).
135 zone_labels (dict): Mapping from zone_id to human-readable label (str).
136 zone_to_cluster (dict): Mapping from zone_id to cluster/group ID (int).
137 save_path (str): File path to save the plotted image.
138 """
139
140 # === Fixed colors by cluster ID ===
141 cluster_color_map = {
142 0: "#d2b48c", # Desert – tan
143 1: "#228B22", # Pasture – green
144 2: "#87CEEB", # Water – light blue
145 3: "#FF69B4", # Riparian – pink
146 4: "#8B0000", # Sensitive riparian – dark red
147 5: "#9370DB", # Town – purple
148 }
149
150 # === Build a color image based on cluster color ===
151 rows, cols = zone_map.shape
152 color_image = np.zeros((rows, cols, 3))
153
154 for i in range(rows):
155 for j in range(cols):
156 zone_id = zone_map[i, j]
157 cluster_id = zone_to_cluster.get(zone_id, 0)
158 hex_color = cluster_color_map.get(cluster_id, "#888888") # default gray
159 rgb = tuple(int(hex_color.lstrip("#")[k:k+2], 16)/255.0 for k in (0, 2, 4))
160 color_image[i, j] = rgb
161
162 # === Plot map ===
163 fig, ax = plt.subplots(figsize=(8, 6))
164 ax.imshow(color_image)
165
166 for zone_id in sorted(np.unique(zone_map)):
167 if zone_id not in zone_labels:
168 continue
169 label = zone_labels[zone_id]
170 group = zone_to_cluster.get(zone_id, "?")
171 label_text = f"{label} ({group})"
172 positions = np.argwhere(zone_map == zone_id)
173 if len(positions) == 0:
174 continue
175 center_i, center_j = positions.mean(axis=0)
176 ax.text(center_j, center_i, label_text, color="white", ha="center", va="center",
177 fontsize=9, fontweight="bold",
178 bbox=dict(facecolor="black", alpha=0.5, boxstyle="round,pad=0.3"))
179
180 ax.set_title("Labeled Grazing & Riparian Zones")
181 ax.axis('off')
182 plt.tight_layout()
183 plt.savefig(save_path)
184 plt.close(fig)
185
186
187
188import matplotlib.pyplot as plt
189import numpy as np
190
191def plot_labeled_zonesold(zone_map, zone_labels, zone_to_cluster, save_path="labeled_zones.png"):
192 """
193 Plots a zone map with human-readable labels (e.g., "A", "B", "Riparian A"),
194 and appends the cluster ID to each label (e.g., "A (0)").
195
196 Parameters:
197 zone_map (np.ndarray): 2D array of zone IDs (integers).
198 zone_labels (dict): Mapping from zone_id to human-readable label (str).
199 zone_to_cluster (dict): Mapping from zone_id to cluster/group ID (int).
200 save_path (str): File path to save the plotted image.
201 """
202 unique_ids = sorted(np.unique(zone_map))
203 color_map = plt.get_cmap('tab20', len(unique_ids))
204
205 fig, ax = plt.subplots(figsize=(8, 6))
206 cax = ax.imshow(zone_map, cmap=color_map, vmin=0, vmax=len(unique_ids) - 1)
207
208 # Add label with group ID
209 for zone_id in unique_ids:
210 if zone_id not in zone_labels:
211 continue
212 label = zone_labels[zone_id]
213 group = zone_to_cluster.get(zone_id, "?")
214 label_text = f"{label} ({group})" # append group ID
215
216 positions = np.argwhere(zone_map == zone_id)
217 if len(positions) == 0:
218 continue
219 center_i, center_j = positions.mean(axis=0)
220 ax.text(center_j, center_i, label_text, color="white", ha="center", va="center",
221 fontsize=9, fontweight="bold",
222 bbox=dict(facecolor="black", alpha=0.5, boxstyle="round,pad=0.3"))
223
224 ax.set_title("Labeled Grazing & Riparian Zones")
225 ax.axis('off')
226 plt.tight_layout()
227 plt.savefig(save_path)
228 plt.close(fig)
229
230
231def assign_zone_labels(zone_to_cluster):
232 """
233 Assigns human-readable labels to each zone based on its cluster type.
234 Riparian zones are labeled like 'Riparian A', 'Riparian B', etc.
235 Other zones are labeled 'A', 'B', etc. by group type.
236
237 Returns:
238 zone_labels (dict): zone_id → label string
239 """
240 label_counts = {} # track how many zones per type
241 zone_labels = {}
242
243 for zone_id, cluster_id in zone_to_cluster.items():
244 land_class = cluster_to_class.get(cluster_id, f"cluster{cluster_id}")
245 count = label_counts.get(land_class, 0)
246 suffix = chr(65 + count) # A, B, C...
247 if "riparian" in land_class:
248 label = f"{land_class.title()} {suffix}"
249 else:
250 label = f"{suffix}"
251 zone_labels[zone_id] = label
252 label_counts[land_class] = count + 1
253
254 return zone_labels
255
256
257def assign_zone_labels_old(zone_to_cluster, cluster_to_class):
258 """
259 Creates a dictionary mapping zone_id → human-readable labels (e.g., "A", "Riparian B").
260
261 Parameters:
262 zone_to_cluster (dict): zone_id → cluster ID
263 cluster_to_class (dict): cluster ID → class name (e.g., "pasture", "riparian")
264
265 Returns:
266 dict: zone_id → human-friendly label
267 """
268 label_counts = {} # Track how many zones per class
269 zone_labels = {}
270
271 for zone_id, cluster_id in zone_to_cluster.items():
272 zone_class = cluster_to_class.get(cluster_id, "Unknown")
273 count = label_counts.get(zone_class, 0)
274
275 # Generate a label like "Riparian A", "Pasture B", etc.
276 letter = chr(ord('A') + count)
277 label = f"{zone_class.capitalize()} {letter}" if zone_class != "Unknown" else f"Zone {zone_id}"
278
279 zone_labels[zone_id] = label
280 label_counts[zone_class] = count + 1
281
282 return zone_labels
283
284import numpy as np
285import pandas as pd
286
287def save_zone_info_to_excel(parcel_map, zone_map, zone_labels, zone_to_cluster, cluster_to_class, save_path="zone_details.xlsx"):
288 """
289 Save detailed zone information to an Excel file.
290
291 Parameters:
292 parcel_map (np.ndarray): 2D array with cluster IDs.
293 zone_map (np.ndarray): 2D array with zone IDs.
294 zone_labels (dict): zone_id → human-friendly label (e.g., "A", "Riparian B")
295 zone_to_cluster (dict): zone_id → cluster ID
296 cluster_to_class (dict): cluster ID → land class string (e.g., "pasture", "riparian")
297 save_path (str): File path to save Excel.
298 """
299 rows, cols = parcel_map.shape
300 data = []
301
302 for i in range(rows):
303 for j in range(cols):
304 zone_id = zone_map[i, j]
305 cluster_id = parcel_map[i, j]
306 label = zone_labels.get(zone_id, "Unknown")
307 zone_class = cluster_to_class.get(cluster_id, "Unknown")
308 data.append({
309 "Row": i,
310 "Col": j,
311 "Zone ID": zone_id,
312 "Zone Label": label,
313 "Cluster ID": cluster_id,
314 "Zone Class": zone_class
315 })
316
317 df = pd.DataFrame(data)
318 df.to_excel(save_path, index=False)
319 print(f"✅ Zone info saved to {save_path}")
320
321
322def override_zone_id_and_label(zone_map, zone_labels, from_id, from_label, to_id, to_label):
323 """
324 Reassigns all parcels with a given zone ID and label to a new zone ID and label.
325
326 Parameters:
327 zone_map (np.ndarray): 2D array with zone IDs.
328 zone_labels (dict): zone_id → label.
329 from_id (int): Zone ID to search for.
330 from_label (str): Must match the current label for that zone.
331 to_id (int): Zone ID to assign.
332 to_label (str): New label for the new zone ID.
333
334 Returns:
335 zone_map (np.ndarray): Updated zone map.
336 zone_labels (dict): Updated labels.
337 """
338 # Step 1: Confirm the label matches
339 if zone_labels.get(from_id) != from_label:
340 print(f"❌ Mismatch: Zone {from_id} label is '{zone_labels.get(from_id)}', not '{from_label}'")
341 return zone_map, zone_labels
342
343 # Step 2: Loop through all parcels
344 for i in range(zone_map.shape[0]):
345 for j in range(zone_map.shape[1]):
346 if zone_map[i, j] == from_id:
347 zone_map[i, j] = to_id # Override ID
348
349 # Step 3: Update label dictionary
350 zone_labels[to_id] = to_label
351 if from_id not in zone_map:
352 zone_labels.pop(from_id, None)
353
354 print(f"✅ Overrode zone {from_id} ('{from_label}') → zone {to_id} ('{to_label}')")
355 return zone_map, zone_labels
356
357
358
359def remap_zone_id_and_label(zone_map, zone_labels, old_zone_id, old_label, new_zone_id, new_label):
360 """
361 For all parcels where zone_id == old_zone_id and label == old_label:
362 → Change zone_id to new_zone_id and label to new_label.
363
364 Args:
365 zone_map (np.ndarray): 2D map of zone IDs.
366 zone_labels (dict): zone_id → label.
367 old_zone_id (int)
368 old_label (str)
369 new_zone_id (int)
370 new_label (str)
371
372 Returns:
373 zone_map, zone_labels
374 """
375 # Only proceed if the label for old_zone_id matches
376 if zone_labels.get(old_zone_id) != old_label:
377 print(f"❌ Skipping: Label mismatch for zone {old_zone_id}")
378 return zone_map, zone_labels
379
380 # Go through every (i,j) and remap matching zones
381 rows, cols = zone_map.shape
382 for i in range(rows):
383 for j in range(cols):
384 if zone_map[i, j] == old_zone_id:
385 zone_map[i, j] = new_zone_id
386
387 # Update the label dictionary
388 zone_labels[new_zone_id] = new_label
389 if old_zone_id in zone_labels:
390 del zone_labels[old_zone_id]
391
392 print(f"✅ Reassigned zone {old_zone_id} ('{old_label}') → {new_zone_id} ('{new_label}')")
393 return zone_map, zone_labels
394
395
396import matplotlib.pyplot as plt
397import numpy as np
398
399def plot_labeled_zones_old(zone_map, zone_labels, save_path="labeled_zones.png"):
400 """
401 Plots a zone map with human-readable labels (e.g., "A", "B", "Riparian A").
402
403 Parameters:
404 zone_map (np.ndarray): 2D array of integers where each unique int is a zone ID.
405 zone_labels (dict): Mapping from zone_id (int) to human label (str).
406 save_path (str): File path to save the plotted image.
407 """
408 unique_ids = sorted(np.unique(zone_map))
409 color_map = plt.get_cmap('tab20', len(unique_ids))
410
411 fig, ax = plt.subplots(figsize=(8, 6))
412 cax = ax.imshow(zone_map, cmap=color_map, vmin=0, vmax=len(unique_ids) - 1)
413
414 # Add human-readable labels at zone centers
415 for zone_id in unique_ids:
416 if zone_id not in zone_labels:
417 continue
418 label = zone_labels[zone_id]
419 positions = np.argwhere(zone_map == zone_id)
420 if len(positions) == 0:
421 continue
422 center_i, center_j = positions.mean(axis=0)
423 ax.text(center_j, center_i, label, color="white", ha="center", va="center",
424 fontsize=9, fontweight="bold", bbox=dict(facecolor="black", alpha=0.5, boxstyle="round,pad=0.3"))
425
426 ax.set_title("Labeled Grazing & Riparian Zones")
427 ax.axis('off')
428 plt.tight_layout()
429 plt.savefig(save_path)
430 plt.close(fig)
431
432
433 