CoolFace
Apppublic

jeffrey1963/Cattle-Elk-R5

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
zone_utils.py433 linesDownload Raw Back to root
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