File size: 8,125 Bytes
a358495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""Graceful fallback compatibility layer for rasterio when blocked by Windows Smart App Control."""
from __future__ import annotations

import sys
import types
import numpy as np
from PIL import Image
from pathlib import Path
from typing import Any

RASTERIO_AVAILABLE = False

try:
    import rasterio
    import rasterio.enums
    import rasterio.features
    import rasterio.transform
    import rasterio.warp
    import rasterio.windows
    import rasterio.shutil
    import rasterio.crs
    RASTERIO_AVAILABLE = True
except Exception as _load_err:
    import logging
    logging.warning(
        "rasterio native binary was blocked by Windows Code Integrity / Smart App Control: %s. "
        "Enabling PIL/NumPy rasterio compatibility fallback.",
        _load_err,
    )

    class DummyResampling:
        nearest = 0
        bilinear = 1
        cubic = 2
        average = 5

    class DummyAffine:
        def __init__(self, a=1.0, b=0.0, c=0.0, d=0.0, e=-1.0, f=0.0):
            self.a, self.b, self.c = a, b, c
            self.d, self.e, self.f = d, e, f

        def __mul__(self, other):
            return self

        @classmethod
        def translation(cls, x, y):
            return cls(1.0, 0.0, x, 0.0, 1.0, y)

        @classmethod
        def scale(cls, sx, sy):
            return cls(sx, 0.0, 0.0, 0.0, sy, 0.0)

    def dummy_from_origin(west, north, xsize, ysize):
        return DummyAffine(xsize, 0.0, west, 0.0, -ysize, north)

    class DummyWindow:
        def __init__(self, col_off=0, row_off=0, width=0, height=0):
            self.col_off = col_off
            self.row_off = row_off
            self.width = width
            self.height = height

    def dummy_window_bounds(window, transform):
        return (0.0, 0.0, 1.0, 1.0)

    def dummy_transform_bounds(src_crs, dst_crs, left, bottom, right, top, **kwargs):
        return (left, bottom, right, top)

    def dummy_shapes(image, mask=None, transform=None, connectivity=4):
        return []

    class DummyDataset:
        def __init__(self, path: Path | str, mode: str = "r", **kwargs):
            self.path = Path(path)
            self.mode = mode
            self.kwargs = kwargs
            self._img = None
            self.count = 3
            self.width = 512
            self.height = 512
            self.crs = "EPSG:4326"
            self.transform = DummyAffine()
            self.bounds = types.SimpleNamespace(left=0.0, bottom=0.0, right=1.0, top=1.0)
            self.subdatasets = []
            self.descriptions = ["red", "green", "blue"]
            self.nodata = None
            self.profile = {"driver": "GTiff", "count": 3, "dtype": "uint8", "width": 512, "height": 512}
            # Mimic rasterio band dtype list
            self.dtypes = ["float32"] * 3
            self.scales = (1.0,) * 3
            self.offsets = (0.0,) * 3

            if "r" in mode and self.path.exists():
                try:
                    self._img = Image.open(self.path)
                    self.width, self.height = self._img.size
                    bands = len(self._img.getbands())
                    self.count = max(1, bands)
                    self.profile["count"] = self.count
                    self.profile["width"] = self.width
                    self.profile["height"] = self.height
                    self.dtypes = ["float32"] * self.count
                    self.scales = (1.0,) * self.count
                    self.offsets = (0.0,) * self.count
                except Exception:
                    pass

        def __enter__(self):
            return self

        def __exit__(self, *args):
            if self._img:
                try:
                    self._img.close()
                except Exception:
                    pass

        def read(self, indexes=None, out_shape=None, masked=False, **kwargs):
            if self._img is None and self.path.exists():
                try:
                    self._img = Image.open(self.path)
                except Exception:
                    pass
            if self._img is not None:
                img_to_read = self._img
                if out_shape is not None and len(out_shape) >= 2:
                    h, w = out_shape[-2], out_shape[-1]
                    img_to_read = self._img.resize((w, h), Image.Resampling.BILINEAR)
                arr = np.array(img_to_read)
                if arr.ndim == 2:
                    arr = arr[None, ...]
                elif arr.ndim == 3:
                    arr = np.moveaxis(arr, -1, 0)
                if indexes is not None:
                    if isinstance(indexes, int):
                        return arr[indexes - 1] if indexes <= len(arr) else arr[0]
                    idx = [i - 1 for i in indexes if 0 <= i - 1 < len(arr)]
                    return arr[idx] if idx else arr
                return arr
            # Default empty raster
            h = out_shape[-2] if out_shape else self.height
            w = out_shape[-1] if out_shape else self.width
            c = self.count
            return np.zeros((c, h, w), dtype="float32")

        def write(self, data, indexes=None):
            pass

        def update_tags(self, *args, **kwargs):
            pass

        def tags(self, band=None):
            return {}

    def dummy_open(path, mode="r", **kwargs):
        return DummyDataset(path, mode, **kwargs)

    def dummy_band(ds, bidx):
        """Stub for rasterio.band() — returns a (dataset, band_index) tuple."""
        return (ds, bidx)

    def dummy_reproject(source, destination=None, src_transform=None, src_crs=None,
                        dst_transform=None, dst_crs=None, src_nodata=None,
                        dst_nodata=None, resampling=None, **kwargs):
        """No-op reproject: fills destination with zeros when rasterio is unavailable."""
        if destination is not None and hasattr(destination, "__setitem__"):
            try:
                destination[:] = 0
            except Exception:
                pass
        return destination, dst_transform

    def dummy_calculate_default_transform(src_crs, dst_crs, width, height, left=None,
                                          bottom=None, right=None, top=None, **kwargs):
        """Returns an identity-like transform stub."""
        return DummyAffine(), width, height

    # Construct mock rasterio package
    r_mod = types.ModuleType("rasterio")
    r_mod.open = dummy_open
    r_mod.band = dummy_band
    r_mod.DatasetBase = DummyDataset
    r_mod.Affine = DummyAffine

    enums_mod = types.ModuleType("rasterio.enums")
    enums_mod.Resampling = DummyResampling
    r_mod.enums = enums_mod

    feat_mod = types.ModuleType("rasterio.features")
    feat_mod.shapes = dummy_shapes
    r_mod.features = feat_mod

    trans_mod = types.ModuleType("rasterio.transform")
    trans_mod.Affine = DummyAffine
    trans_mod.from_origin = dummy_from_origin
    r_mod.transform = trans_mod

    win_mod = types.ModuleType("rasterio.windows")
    win_mod.Window = DummyWindow
    win_mod.bounds = dummy_window_bounds
    win_mod.window_bounds = dummy_window_bounds
    r_mod.windows = win_mod

    warp_mod = types.ModuleType("rasterio.warp")
    warp_mod.transform_bounds = dummy_transform_bounds
    warp_mod.reproject = dummy_reproject
    warp_mod.calculate_default_transform = dummy_calculate_default_transform
    warp_mod.Resampling = DummyResampling
    r_mod.warp = warp_mod

    crs_mod = types.ModuleType("rasterio.crs")
    crs_mod.CRS = types.SimpleNamespace(from_epsg=lambda code: f"EPSG:{code}")
    r_mod.crs = crs_mod

    shutil_mod = types.ModuleType("rasterio.shutil")
    shutil_mod.copy = lambda src, dst, **k: None
    r_mod.shutil = shutil_mod

    # Register into sys.modules
    sys.modules["rasterio"] = r_mod
    sys.modules["rasterio.enums"] = enums_mod
    sys.modules["rasterio.features"] = feat_mod
    sys.modules["rasterio.transform"] = trans_mod
    sys.modules["rasterio.windows"] = win_mod
    sys.modules["rasterio.warp"] = warp_mod
    sys.modules["rasterio.crs"] = crs_mod
    sys.modules["rasterio.shutil"] = shutil_mod