wolfhece 2.1.45__py3-none-any.whl → 2.1.48__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ from pyproj import Transformer
2
+ from concurrent.futures import ProcessPoolExecutor
3
+ import numpy as np
4
+ from osgeo import gdal
5
+ import os
6
+ import logging
7
+
8
+ from .PyTranslate import _
9
+
10
+ def transform_chunk_coordinates(inputEPSG:str, outputEPSG:str, chunk:np.ndarray):
11
+ """
12
+ Transforms a chunk of coordinates
13
+
14
+ :param inputEPSG: input EPSG code (e.g. "EPSG:3812")
15
+ :type inputEPSG: str
16
+ :param outputEPSG: output EPSG code (e.g. "EPSG:31370")
17
+ :type outputEPSG: str
18
+ :param chunk: list of points to be transformed
19
+ :type chunk: np.ndarray
20
+ """
21
+ COO_TRANSFORMER = Transformer.from_crs(inputEPSG, outputEPSG, always_xy=True)
22
+ return COO_TRANSFORMER.transform(chunk[:, 0], chunk[:, 1])
23
+
24
+ def transform_coordinates(points:np.ndarray, inputEPSG:str="EPSG:3812", outputEPSG:str="EPSG:31370", chunk_size:int=1000):
25
+ """
26
+ Transforms coordinates in batches using multiprocessing. If more than chunk_size points are provided, the
27
+ function will split the points into chunks and transform them in parallel => requiring in the main script
28
+ to use the statement if __name__ == '__main__':.
29
+
30
+ :param points: Array of coordinates to be transformed
31
+ :type points: numpy.ndarray
32
+ :param inputEPSG: (optional) Input EPSG code. Defaults to "EPSG:3812"
33
+ :type inputEPSG: str
34
+ :param outputEPSG: (optional) Output EPSG code. Defaults to "EPSG:31370"
35
+ :type outputEPSG: str
36
+ :param chunk_size: (optional) Size of each batch for transformation. Defaults to 100000
37
+ :type chunk_size: int
38
+
39
+ :return numpy.ndarray: Transformed coordinates
40
+ """
41
+
42
+ # sanitize inputs
43
+ inputEPSG = str(inputEPSG)
44
+ outputEPSG = str(outputEPSG)
45
+
46
+ if not "EPSG" in inputEPSG or not "EPSG" in inputEPSG:
47
+ logging.error(_("EPSG code must be in the format 'EPSG:XXXX'"))
48
+ return
49
+
50
+ num_points = len(points)
51
+ results = []
52
+
53
+ total_steps = (num_points + chunk_size - 1) // chunk_size
54
+
55
+ if total_steps == 1:
56
+ result_x, result_y = transform_chunk_coordinates(inputEPSG, outputEPSG, points)
57
+ return np.vstack((result_x, result_y)).T
58
+
59
+ with ProcessPoolExecutor() as executor:
60
+ futures = []
61
+ for i in range(0, num_points, chunk_size):
62
+ chunk = points[i:i + chunk_size]
63
+ futures.append(executor.submit(transform_chunk_coordinates, inputEPSG, outputEPSG, chunk))
64
+
65
+ for step, future in enumerate(futures):
66
+ result_x, result_y = future.result()
67
+ results.append(np.vstack((result_x, result_y)).T)
68
+
69
+ return np.vstack(results)
70
+
71
+ def reproject_and_resample_raster(input_raster_path:str, output_raster_path:str,
72
+ input_srs:str='EPSG:3812', output_srs:str='EPSG:31370',
73
+ resampling_method:str|int=gdal.GRA_Bilinear,
74
+ xRes:float=0.5, yRes:float=0.5, debug:bool=False):
75
+ """
76
+ Use gdal to open a tiff raster in a given input EPSG 'inputEPSG' and transforms the raster into another EPSG system 'outputEPSG'.
77
+ The resolution can be forced through xRes and yRes (the origin will be rounded to the nearest multiple of the resolution). The
78
+ resampling method can be chosen among the gdal GRA_* constants (gdal.GRA_Average; gdal.GRA_Bilinear; gdal.GRA_Cubic; gdal.GRA_CubicSpline;
79
+ gdal.GRA_Lanczos; gdal.GRA_Mode; gdal.GRA_NearestNeighbor).
80
+
81
+ :param input_raster_path: the path to the input raster file (.tif or .tiff)
82
+ :type input_raster_path: str
83
+ :param output_raster_path: the path to the output raster file (.tif or .tiff) that will be created
84
+ :type output_raster_path: str
85
+ :param input_srs: Input EPSG code. Defaults to Lambert 2008 "EPSG:3812"
86
+ :type input_srs: str
87
+ :param output_srs: Output EPSG code. Defaults to Lambert 72 "EPSG:31370"
88
+ :type output_srs: str
89
+ :param resampling_method: Resampling method. Defaults to gdal.GRA_Bilinear
90
+ :type resampling_method: int, str
91
+ :param xRes: Resolution along X. Defaults to 0.5
92
+ :type xRes: float
93
+ :param yRes: Resolution along Y. Defaults to 0.5
94
+ :type yRes: float
95
+ :param debug: If True, print debug information. Defaults to False
96
+ :type debug: bool
97
+ """
98
+ from osgeo import osr
99
+
100
+ # santitize inputs
101
+ input_raster_path = str(input_raster_path)
102
+ output_raster_path = str(output_raster_path)
103
+
104
+ # ATTENTION: mask values should be negative in the tiff corresponding to input_raster_path!
105
+ if not(input_raster_path.endswith('.tif') or input_raster_path.endswith('.tiff')):
106
+ logging.error(_("Input raster must be a GeoTIFF file"))
107
+ return
108
+ if not(output_raster_path.endswith('.tif') or output_raster_path.endswith('.tiff')):
109
+ logging.error(_("Output raster must be a GeoTIFF file"))
110
+ return
111
+
112
+ # check the output file
113
+ if os.path.exists(output_raster_path):
114
+ try:
115
+ os.remove(output_raster_path)
116
+ except PermissionError as e:
117
+ logging.error(_(f"Permission denied while trying to delete {output_raster_path}. Ensure the file is not open in another program and you have sufficient privileges."))
118
+ return
119
+ except Exception as e:
120
+ logging.error(_(f"An unexpected error occurred while trying to delete {output_raster_path}: {str(e)}"))
121
+ return
122
+
123
+ # Open the input raster
124
+ input_raster = gdal.Open(input_raster_path)
125
+ if input_raster is None:
126
+ logging.error(_(f"Unable to open input raster: {input_raster_path}"))
127
+ return
128
+
129
+ # Get the source SRS from the input raster
130
+ source_srs = osr.SpatialReference()
131
+ if debug:
132
+ print('Initial projection: ',input_raster.GetProjection())
133
+ print('set projection init: ',int(input_srs.split(':')[1]))
134
+ source_srs.ImportFromEPSG(int(input_srs.split(':')[1]))
135
+
136
+ # Create the target SRS
137
+ target_srs = osr.SpatialReference()
138
+ if debug:
139
+ print('set projection out: ',int(output_srs.split(':')[1]))
140
+ target_srs.ImportFromEPSG(int(output_srs.split(':')[1]))
141
+
142
+ # Define the options for the reprojection
143
+ # Load the initial array to obtain the origin and the limits
144
+ ulx, xres, xskew, uly, yskew, yres = input_raster.GetGeoTransform()
145
+ Orig = np.array([[ulx,uly],
146
+ [ulx+input_raster.RasterXSize*xres, uly+input_raster.RasterYSize*yres]])
147
+ Orig.sort(0)
148
+
149
+ # Transform the origin and the limits into the new projection 'Lambert 72'
150
+ Orig_out = transform_coordinates(Orig, inputEPSG=input_srs, outputEPSG=output_srs)
151
+
152
+ # Round each coordinate to the nearest multiple of the wanted resolution
153
+ Orig_out[:,0] = np.round(Orig_out[:,0]/xRes)*xRes
154
+ Orig_out[:,1] = np.round(Orig_out[:,1]/yRes)*yRes
155
+ if debug:
156
+ print(Orig_out)
157
+ print(tuple(Orig_out.reshape(-1)))
158
+
159
+ # Define the reprojection options
160
+ # outputBounds=tuple(Orig_out.reshape(-1)),
161
+ # xRes=xRes, yRes=yRes,
162
+ reproject_options = gdal.WarpOptions(
163
+ outputBounds=tuple(Orig_out.reshape(-1)), # Output bounds: (minX, minY, maxX, maxY)
164
+ xRes=xRes, yRes=yRes,
165
+ srcSRS=source_srs.ExportToWkt(),
166
+ dstSRS=target_srs.ExportToWkt(),
167
+ resampleAlg=resampling_method
168
+ )
169
+
170
+ # Reproject and resample the input raster
171
+ output_raster = gdal.Warp(
172
+ destNameOrDestDS=output_raster_path,
173
+ srcDSOrSrcDSTab=input_raster,
174
+ options=reproject_options
175
+ )
176
+
177
+ if output_raster is None:
178
+ logging.error(_(f"Reprojection failed for input raster: {input_raster_path}"))
179
+ return
180
+
181
+ # Flush cache to ensure the output is written to disk
182
+ output_raster.FlushCache()
183
+
184
+ # Close the datasets IMPORTANT to set to None in order to close them
185
+ input_raster = None
186
+ output_raster = None
187
+
188
+ if debug:
189
+ print(f"Reprojection and resampling completed successfully. Output saved to: {output_raster_path}")
wolfhece/PyDraw.py CHANGED
@@ -7508,6 +7508,8 @@ class WolfMapViewer(wx.Frame):
7508
7508
  if self.active_array.nb_blocks > 0:
7509
7509
  txt += ' ; Nb blocks : {:d}'.format(self.active_array.nb_blocks)
7510
7510
 
7511
+ txt += ' ; Type : ' + self.active_array.dtype_str
7512
+
7511
7513
  self.StatusBar.SetStatusText(txt)
7512
7514
 
7513
7515
 
@@ -8393,6 +8395,8 @@ class WolfMapViewer(wx.Frame):
8393
8395
  if not altdown:
8394
8396
  newarray.mask_outsidepoly(self.active_vector)
8395
8397
 
8398
+ newarray.nullify_border(width=1)
8399
+
8396
8400
  self.add_object('array', newobj = newarray, id = self.active_array.idx + '_crop')
8397
8401
 
8398
8402
  self.Refresh()
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 1
8
- self.patch = 45
8
+ self.patch = 48
9
9
 
10
10
  def __str__(self):
11
11
 
@@ -746,6 +746,7 @@ class Config_Manager_2D_GPU:
746
746
  new_zones.add_zone(new_zone)
747
747
 
748
748
  curarray = WolfArray(curtif)
749
+ curarray.nullify_border(width=1)
749
750
  sux, sux, curvect, interior = curarray.suxsuy_contour()
750
751
  new_zone.add_vector(curvect, forceparent=True)
751
752
  curvect.set_legend_to_centroid(curtif.name)
@@ -1481,6 +1482,10 @@ class UI_Manager_2D_GPU():
1481
1482
  logging.info(_('Creating vecz ...'))
1482
1483
  mydata = self._treelist.GetItemData(self._selected_item)
1483
1484
 
1485
+ if not 'path' in mydata:
1486
+ logging.error(_('Please select a scenario to analyze !'))
1487
+ return
1488
+
1484
1489
  # création du fichier vrt
1485
1490
  new_zones = self._parent.create_vec(mydata['path'])
1486
1491
  logging.info(_('... done !'))
wolfhece/wolf_array.py CHANGED
@@ -20,6 +20,7 @@ import numpy.ma as ma
20
20
  import math as m
21
21
  import logging
22
22
  import json
23
+ import tempfile
23
24
  from pathlib import Path
24
25
 
25
26
  try:
@@ -45,7 +46,11 @@ from os.path import dirname,basename,join
45
46
  import logging
46
47
  from typing import Literal
47
48
  from copy import deepcopy
49
+ from osgeo import gdal
50
+ from enum import Enum
48
51
 
52
+
53
+ from .Coordinates_operations import reproject_and_resample_raster
49
54
  from .PyTranslate import _
50
55
  from .GraphNotebook import PlotPanel
51
56
  from .CpGrid import CpGrid
@@ -90,6 +95,42 @@ WOLF_ARRAY_MB = [WOLF_ARRAY_MB_SINGLE, WOLF_ARRAY_MB_INTEGER, WOLF_ARRAY_MNAP_IN
90
95
 
91
96
  VERSION_RGB = 2
92
97
 
98
+ class Rebin_Ops(Enum):
99
+ MIN = 0
100
+ MEAN = 1
101
+ MAX = 2
102
+ SUM = 3
103
+ MEDIAN = 4
104
+
105
+ @classmethod
106
+ def get_numpy_ops(cls):
107
+ """ Return a list of numpy functions corresponding to the enum values """
108
+
109
+ # CAUTION : Order is important and must match the enum values
110
+ return [np.ma.min, np.ma.mean, np.ma.max, np.ma.sum, np.ma.median]
111
+
112
+ @classmethod
113
+ def get_ops(cls, name:str):
114
+ """ Return the numpy function corresponding to a string """
115
+
116
+ if isinstance(name, Rebin_Ops):
117
+ return cls.get_numpy_ops()[name.value]
118
+ elif isinstance(name, str):
119
+ if name == 'min':
120
+ return np.ma.min
121
+ elif name == 'mean':
122
+ return np.ma.mean
123
+ elif name == 'max':
124
+ return np.ma.max
125
+ elif name == 'sum':
126
+ return np.ma.sum
127
+ elif name == 'median':
128
+ return np.ma.median
129
+ else:
130
+ return None
131
+ else:
132
+ return None
133
+
93
134
  def getkeyblock(i, addone=True) -> str:
94
135
  """
95
136
  Name/Key of a block in the dictionnary of a WolfArrayMB instance
@@ -111,6 +152,9 @@ def decodekeyblock(key, addone=True) -> int:
111
152
  return int(key[5:])
112
153
  else:
113
154
  return int(key[5:]) - 1
155
+
156
+
157
+
114
158
  class header_wolf():
115
159
  """
116
160
  Header of WolfArray
@@ -284,9 +328,9 @@ class header_wolf():
284
328
  """
285
329
  Set translation
286
330
 
287
- :param tr_x = translation along X
288
- :param tr_y = translation along Y
289
- :param tr_z = translation along Z
331
+ :param tr_x: translation along X
332
+ :param tr_y: translation along Y
333
+ :param tr_z: translation along Z
290
334
  """
291
335
  self.translx = tr_x
292
336
  self.transly = tr_y
@@ -4396,14 +4440,24 @@ class WolfArray(Element_To_Draw, header_wolf):
4396
4440
 
4397
4441
  def __del__(self):
4398
4442
  """ Destructeur de la classe """
4399
- import gc
4400
- self.delete_lists()
4401
- del self.array
4402
- if VERSION_RGB==1 : del self.rgb
4403
- del self._array3d
4404
- del self.mypal
4405
- del self.shaded
4406
- gc.collect()
4443
+ try:
4444
+ # Perform cleanup tasks safely
4445
+ self.delete_lists()
4446
+ if hasattr(self, 'array'):
4447
+ del self.array
4448
+ if VERSION_RGB == 1 and hasattr(self, 'rgb'):
4449
+ del self.rgb
4450
+ if hasattr(self, '_array3d'):
4451
+ del self._array3d
4452
+ if hasattr(self, 'mypal'):
4453
+ del self.mypal
4454
+ if hasattr(self, 'shaded'):
4455
+ del self.shaded
4456
+ # Perform garbage collection if gc is available
4457
+ import gc
4458
+ gc.collect()
4459
+ except Exception as e:
4460
+ print(f"Exception in WolfArray destructor: {e} -- Please report this issue")
4407
4461
 
4408
4462
  def extract_selection(self):
4409
4463
  """ Extract the current selection """
@@ -4613,6 +4667,36 @@ class WolfArray(Element_To_Draw, header_wolf):
4613
4667
 
4614
4668
  return dtype
4615
4669
 
4670
+ @property
4671
+ def dtype_str(self):
4672
+ """
4673
+ Return the numpy dtype corresponding to the WOLF type, as a string
4674
+
4675
+ Pay ettention to the difference between :
4676
+ - LOGICAL : Fortran and VB6
4677
+ - Bool : Python
4678
+
4679
+ In VB6, logical is stored as int16
4680
+ In Fortran, there are Logical*1, Logical*2, Logical*4, Logical*8
4681
+ In Python, bool is one byte
4682
+ In Numpy, np.bool_ is one byte
4683
+ """
4684
+
4685
+ if self.wolftype in [WOLF_ARRAY_FULL_DOUBLE, WOLF_ARRAY_SYM_DOUBLE, WOLF_ARRAY_CSR_DOUBLE]:
4686
+ dtype = _('float64 - 8 bytes poer values')
4687
+ elif self.wolftype in [WOLF_ARRAY_FULL_SINGLE, WOLF_ARRAY_FULL_SINGLE_3D, WOLF_ARRAY_MB_SINGLE]:
4688
+ dtype = _('float32 - 4 bytes per values')
4689
+ elif self.wolftype in [WOLF_ARRAY_FULL_INTEGER, WOLF_ARRAY_MB_INTEGER, WOLF_ARRAY_MNAP_INTEGER]:
4690
+ dtype = _('int32 - 4 bytes per values')
4691
+ elif self.wolftype in [WOLF_ARRAY_FULL_INTEGER16, WOLF_ARRAY_FULL_INTEGER16_2]:
4692
+ dtype = _('int16 - 2 bytes per values')
4693
+ elif self.wolftype == WOLF_ARRAY_FULL_INTEGER8:
4694
+ dtype = _('int8 - 1 byte per values')
4695
+ elif self.wolftype == WOLF_ARRAY_FULL_LOGICAL:
4696
+ dtype = _('int16 - 2 bytes per values')
4697
+
4698
+ return dtype
4699
+
4616
4700
  def loadnap_and_apply(self):
4617
4701
  """
4618
4702
  Load a mask file (aka nap) and apply it to the array;
@@ -4772,7 +4856,7 @@ class WolfArray(Element_To_Draw, header_wolf):
4772
4856
  if reset_plot:
4773
4857
  self.reset_plot()
4774
4858
 
4775
- def export_geotif(self, outdir='', extent = ''):
4859
+ def export_geotif(self, outdir='', extent = '', EPSG:int = 31370):
4776
4860
  """
4777
4861
  Export de la matrice au format Geotiff (Lambert 72 - EPSG:31370)
4778
4862
 
@@ -4783,11 +4867,12 @@ class WolfArray(Element_To_Draw, header_wolf):
4783
4867
 
4784
4868
  :param outdir: directory
4785
4869
  :param extent: suffix to add to the filename before the extension '.tif'
4870
+ :param EPSG: EPSG code, by default 31370 (Lambert 72)
4786
4871
  """
4787
4872
  from osgeo import gdal, osr, gdalconst
4788
4873
 
4789
4874
  srs = osr.SpatialReference()
4790
- srs.ImportFromEPSG(31370)
4875
+ srs.ImportFromEPSG(EPSG)
4791
4876
 
4792
4877
  if outdir=='' and extent=='':
4793
4878
  filename = self.filename
@@ -4815,7 +4900,17 @@ class WolfArray(Element_To_Draw, header_wolf):
4815
4900
  out_ds: gdal.Dataset
4816
4901
  band: gdal.Band
4817
4902
  driver = gdal.GetDriverByName("GTiff")
4818
- out_ds = driver.Create(filename, arr.shape[0], arr.shape[1], 1, arr_type, options=['COMPRESS=LZW'])
4903
+ # bytes_per_pixel = arr.data.dtype.itemsize
4904
+ estimated_file_size = self.memory_usage #arr.shape[0] * arr.shape[1] * bytes_per_pixel
4905
+
4906
+ # Check if estimated file size exceeds 4GB
4907
+ if (estimated_file_size > 4 * 1024**3): # 4GB in bytes
4908
+ options = ['COMPRESS=LZW', 'BIGTIFF=YES']
4909
+ print('BigTIFF format will be used!')
4910
+ else:
4911
+ options = ['COMPRESS=LZW']
4912
+
4913
+ out_ds = driver.Create(filename, arr.shape[0], arr.shape[1], 1, arr_type, options=options)
4819
4914
  out_ds.SetProjection(srs.ExportToWkt())
4820
4915
 
4821
4916
 
@@ -5079,6 +5174,10 @@ class WolfArray(Element_To_Draw, header_wolf):
5079
5174
  except:
5080
5175
  logging.warning(_('Error during importing tif file'))
5081
5176
 
5177
+ # Close the raster
5178
+ raster.FlushCache()
5179
+ raster = None
5180
+
5082
5181
  def add_ops_sel(self):
5083
5182
  """
5084
5183
  Adding selection manager and operations array
@@ -6652,11 +6751,12 @@ class WolfArray(Element_To_Draw, header_wolf):
6652
6751
  if update_min_max:
6653
6752
  self.mypal.distribute_values(self.array.min(), self.array.max())
6654
6753
 
6655
- def write_all(self, newpath:str = None):
6754
+ def write_all(self, newpath:str = None, EPSG:int = 31370):
6656
6755
  """
6657
6756
  Ecriture de tous les fichiers d'un Wolf array
6658
6757
 
6659
6758
  :param newpath: new path and filename with extension -- if None, use the current filename
6759
+ :param EPSG: EPSG code for geotiff
6660
6760
  """
6661
6761
 
6662
6762
  if isinstance(newpath, Path):
@@ -6666,7 +6766,7 @@ class WolfArray(Element_To_Draw, header_wolf):
6666
6766
  self.filename = newpath
6667
6767
 
6668
6768
  if self.filename.endswith('.tif'):
6669
- self.export_geotif()
6769
+ self.export_geotif(EPSG=EPSG)
6670
6770
  elif self.filename.endswith('.npy'):
6671
6771
 
6672
6772
  writing_header = True
@@ -6700,18 +6800,26 @@ class WolfArray(Element_To_Draw, header_wolf):
6700
6800
  self.write_txt_header()
6701
6801
  self.write_array()
6702
6802
 
6703
- def rebin(self, factor:float, operation:Literal['mean', 'sum', 'min']='mean') -> None:
6803
+ def get_rebin_shape_size(self, factor:float) -> tuple[tuple[int, int], tuple[float, float]]:
6704
6804
  """
6705
- Change resolution - in place
6805
+ Return the new shape after rebinning.
6706
6806
 
6707
- :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6807
+ newdx = dx * factor
6808
+ newdy = dy * factor
6809
+
6810
+ The shape is adjusted to be a multiple of the factor.
6708
6811
 
6709
- If you want to keep current data, copy the WolfArray into a new variable -> newWA = Wolfarray(mold=curWA)
6812
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6813
+ :type factor: float
6814
+ :return: new shape
6815
+ :rtype: Tuple[int, int]
6710
6816
  """
6711
- operation = operation.lower()
6712
- if not operation in ['sum', 'mean', 'min']:
6713
- raise ValueError("Operator not supported.")
6714
6817
 
6818
+ newdx = self.dx * float(factor)
6819
+ newdy = self.dy * float(factor)
6820
+
6821
+ newnbx = self.nbx
6822
+ newnby = self.nby
6715
6823
  if np.mod(self.nbx,factor) != 0 or np.mod(self.nby,factor) != 0 :
6716
6824
  newnbx = self.nbx
6717
6825
  newnby = self.nby
@@ -6720,8 +6828,85 @@ class WolfArray(Element_To_Draw, header_wolf):
6720
6828
  if np.mod(self.nby,factor) !=0:
6721
6829
  newnby = self.nby + factor - np.mod(self.nby,factor)
6722
6830
 
6723
- newarray = np.ma.zeros((newnbx,newnby))
6831
+ newnbx = int(newnbx / factor)
6832
+ newnby = int(newnby / factor)
6833
+
6834
+ return (newnbx, newnby), (newdx, newdy)
6835
+
6836
+ def get_rebin_header(self, factor:float) -> header_wolf:
6837
+ """
6838
+ Return a new header after rebinning.
6839
+
6840
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6841
+ :type factor: float
6842
+
6843
+ :return: new header
6844
+ :rtype: header_wolf
6845
+ """
6846
+
6847
+ newshape, newdx_dy = self.get_rebin_shape_size(factor)
6848
+
6849
+ newheader = self.get_header()
6850
+
6851
+ newheader.nbx = newshape[0]
6852
+ newheader.nby = newshape[1]
6853
+ newheader.dx = newdx_dy[0]
6854
+ newheader.dy = newdx_dy[1]
6855
+
6856
+ return newheader
6857
+
6858
+ def rebin(self,
6859
+ factor:float,
6860
+ operation:Literal['mean', 'sum', 'min', 'max', 'median'] ='mean',
6861
+ operation_matrix:"WolfArray"=None) -> None:
6862
+ """
6863
+ Change resolution - **in place**.
6864
+
6865
+ If you want to keep current data, copy the WolfArray into a new variable -> newWA = Wolfarray(mold=curWA).
6866
+
6867
+ :param factor: factor of resolution change -- > 1.0 : decrease resolution, < 1.0 : increase resolution
6868
+ :type factor: float
6869
+ :param operation: operation to apply on the blocks ('mean', 'sum', 'min', 'max', 'median')
6870
+ :type operation: str, Rebin_Ops
6871
+ :param operation_matrix: operation matrix to apply on the blocks -- see the Enum "Rebin_Ops" for more infos. The matrix must have the same shape as the new array
6872
+ :type operation_matrix: WolfArray
6873
+
6874
+ """
6875
+
6876
+ if operation_matrix is not None:
6877
+ tmp_header = self.get_rebin_header(factor)
6878
+ if not operation_matrix.is_like(tmp_header):
6879
+ logging.error(_("The operation matrix must have the same shape as the new array"))
6880
+ logging.info(_("You can use the get_rebin_header method to get the new header if you don't know it"))
6881
+ return
6882
+
6883
+ logging.info(_("Operation matrix detected"))
6884
+ logging.info(_("The operation matrix will be used to apply the operation on the blocks"))
6885
+ else:
6886
+
6887
+ operation = Rebin_Ops.get_ops(operation)
6888
+
6889
+ if operation is None:
6890
+ logging.error(_("Operator not supported -- Must be a string in ['sum', 'mean', 'min', 'max', 'median'] or a Rebin_Ops Enum"))
6891
+ return
6892
+
6893
+ if not callable(operation):
6894
+ logging.error(_("Operator not supported -- Must be a string in ['sum', 'mean', 'min', 'max', 'median'] or a Rebin_Ops Enum"))
6895
+
6896
+
6897
+ if np.mod(self.nbx,factor) != 0 or np.mod(self.nby,factor) != 0 :
6898
+ # The shape is adjusted to be a multiple of the factor.
6899
+ # Fill the array with nullvalue
6900
+ newnbx = self.nbx
6901
+ newnby = self.nby
6902
+ if np.mod(self.nbx,factor) !=0:
6903
+ newnbx = int(self.nbx + factor - np.mod(self.nbx,factor))
6904
+ if np.mod(self.nby,factor) !=0:
6905
+ newnby = int(self.nby + factor - np.mod(self.nby,factor))
6906
+
6907
+ newarray = np.ma.ones((newnbx,newnby), dtype = self.dtype) * self.nullvalue
6724
6908
  newarray[:self.nbx,:self.nby] = self.array
6909
+ newarray.mask[:self.nbx,:self.nby] = self.array.mask
6725
6910
  self.array = newarray
6726
6911
 
6727
6912
  self.nbx = newnbx
@@ -6735,20 +6920,41 @@ class WolfArray(Element_To_Draw, header_wolf):
6735
6920
  new_shape = (self.nbx, self.nby)
6736
6921
 
6737
6922
  if factor>1.:
6738
- compression_pairs = [(d, c // d) for d, c in zip(new_shape,
6739
- self.array.shape)]
6740
- flattened = [l for p in compression_pairs for l in p]
6741
- self.array = self.array.reshape(flattened)
6742
- for i in range(len(new_shape)):
6743
- op = getattr(self.array, operation)
6744
- self.array = np.float32(op(-1 * (i + 1)))
6923
+ if operation_matrix is not None:
6924
+ # Reshape the input array to split it into blocks of size f x f
6925
+ reshaped_a = self.array.reshape(new_shape[0], int(factor), new_shape[1], int(factor))
6926
+
6927
+ # Swap axes to make blocks as separate dimensions
6928
+ reshaped_a = reshaped_a.swapaxes(1, 2)
6929
+
6930
+ # Initialize the output matrix
6931
+ self.array = ma.masked_array(np.ones((new_shape[0], new_shape[1]), dtype= self.dtype) * self.nullvalue, dtype= self.dtype)
6932
+
6933
+ # Check the dtype of the newly initialized array
6934
+ assert self.array.dtype == self.dtype, _('Bad dtype')
6935
+
6936
+ # Vectorized operations
6937
+ for op_idx, operation in enumerate(Rebin_Ops.get_numpy_ops()):
6938
+ mask = (operation_matrix.array == op_idx)
6939
+ if np.any(mask):
6940
+ block_results = operation(reshaped_a, axis=(2, 3))
6941
+ self.array[mask] = block_results[mask]
6942
+
6943
+ else:
6944
+ compression_pairs = [(d, c // d) for d, c in zip(new_shape,
6945
+ self.array.shape)]
6946
+ flattened = [l for p in compression_pairs for l in p]
6947
+ self.array = operation(self.array.reshape(flattened), axis=(1, 3)).astype(self.dtype)
6948
+
6949
+ self.set_nullvalue_in_mask()
6745
6950
  else:
6746
6951
  self.array = np.kron(self.array, np.ones((int(1/factor), int(1/factor)), dtype=self.array.dtype))
6747
6952
 
6748
- self.mask_reset()
6749
-
6750
6953
  self.count()
6751
6954
 
6955
+ # rebin must not change the type of the array
6956
+ assert self.array.dtype == self.dtype, _('Bad dtype')
6957
+
6752
6958
  def read_txt_header(self):
6753
6959
  """
6754
6960
  Read header from txt file
@@ -7940,26 +8146,85 @@ class WolfArray(Element_To_Draw, header_wolf):
7940
8146
 
7941
8147
  def map_values(self, keys_vals:dict, default:float=None):
7942
8148
  """
7943
- Apply a mapping to the array
8149
+ Mapping array values to new values defined by a dictionnary.
8150
+
8151
+ First, check if all values are in keys_vals. If not, set to default.
8152
+ If default is None, set to nullvalue.
8153
+
8154
+ :param keys_vals: dictionary of values to map
8155
+ :param default: default value if key not found
7944
8156
  """
7945
8157
 
7946
8158
  vals = self.get_unique_values()
7947
8159
 
7948
- if default is not None:
7949
- self.array.data[:,:] = default
7950
-
8160
+ def_keys = []
7951
8161
  for val in vals:
7952
8162
  if val not in keys_vals:
7953
- logging.warning(f"Value {val} not in keys_vals")
8163
+ logging.warning(_(f"Value {val} not in keys_vals -- Will be set to default or NullValue"))
8164
+ def_keys.append(val)
7954
8165
  continue
7955
8166
 
7956
8167
  for key, val in keys_vals.items():
7957
8168
  self.array.data[self.array.data == key] = val
7958
8169
 
8170
+ if default is None:
8171
+ default = self.nullvalue
8172
+
8173
+ for key in def_keys:
8174
+ self.array.data[self.array.data == key] = default
8175
+
7959
8176
  self.mask_data(self.nullvalue)
7960
8177
 
7961
8178
  self.reset_plot()
7962
8179
 
8180
+ @classmethod
8181
+ def from_other_epsg_coo(cls,
8182
+ input_raster_path:str,
8183
+ input_srs='EPSG:3812',
8184
+ output_srs='EPSG:31370',
8185
+ resampling_method=gdal.GRA_Bilinear,
8186
+ xRes:float=0.5, yRes:float=0.5):
8187
+ """
8188
+ Reprojects and resamples a raster file from an other EPSG coordinates and return it as a WolfArray.
8189
+
8190
+ :param input_raster_path: The path to the input raster file.
8191
+ :type input_raster_path: str
8192
+ :param input_srs: The input spatial reference system (SRS) in the format 'EPSG:XXXX'. Defaults to Lambert 2008 'EPSG:3812'.
8193
+ :type input_srs: str
8194
+ :param output_srs: The output spatial reference system (SRS) in the format 'EPSG:XXXX'. Defaults to Belgian Lambert 72 'EPSG:31370'.
8195
+ :type output_srs: str
8196
+ :param resampling_method: The resampling method to use. Defaults to gdal.GRA_Bilinear. Resampling method can be chosen among the gdal GRA_*
8197
+ constants (gdal.GRA_Average; gdal.GRA_Bilinear; gdal.GRA_Cubic; gdal.GRA_CubicSpline;
8198
+ gdal.GRA_Lanczos; gdal.GRA_Mode; gdal.GRA_NearestNeighbour)
8199
+ :type resampling_method: int
8200
+ :param xRes: The desired output resolution in the x direction. Defaults to 0.5.
8201
+ :type xRes (float): float
8202
+ :param yRes: The desired output resolution in the y direction. Defaults to 0.5.
8203
+ :type yRes (float): float
8204
+
8205
+ :raises AssertionError: If the input or output raster file is not a GeoTIFF file.
8206
+ :raises RuntimeError: If the input raster file cannot be opened.
8207
+ :raises PermissionError: If there is a permission error while trying to delete the output raster file.
8208
+ :raises Exception: If an unexpected error occurs while trying to delete the output raster file.
8209
+ :raises RuntimeError: If the reprojection fails for the input raster file.
8210
+
8211
+ :return: WolfArray
8212
+ """
8213
+
8214
+ #sanitize input
8215
+ input_raster_path = str(input_raster_path)
8216
+ input_srs = str(input_srs)
8217
+ output_srs = str(output_srs)
8218
+
8219
+ assert resampling_method in [gdal.GRA_Average, gdal.GRA_Bilinear, gdal.GRA_Cubic, gdal.GRA_CubicSpline, gdal.GRA_Lanczos, gdal.GRA_Mode, gdal.GRA_NearestNeighbour], "Invalid resampling method"
8220
+
8221
+ # Define temporary files
8222
+ with tempfile.TemporaryDirectory() as temp_dir:
8223
+ output_raster_path = os.path.join(temp_dir, "Array_72.tif")
8224
+ reproject_and_resample_raster(input_raster_path, output_raster_path, input_srs, output_srs, resampling_method, xRes, yRes)
8225
+ Array3 = WolfArray(output_raster_path, nullvalue=-9999)
8226
+ return Array3
8227
+
7963
8228
  class WolfArrayMB(WolfArray):
7964
8229
  """
7965
8230
  Matrice multiblocks
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wolfhece
3
- Version: 2.1.45
3
+ Version: 2.1.48
4
4
  Author-email: Pierre Archambeau <pierre.archambeau@uliege.be>
5
5
  License: Copyright (c) 2024 University of Liege. All rights reserved.
6
6
  Project-URL: Homepage, https://uee.uliege.be/hece
@@ -13,14 +13,14 @@ Classifier: Topic :: Scientific/Engineering :: Physics
13
13
  Requires-Python: <3.11,>=3.10
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: wxpython
16
- Requires-Dist: colorlog
16
+ Requires-Dist: colorlog ==6.7.*
17
17
  Requires-Dist: intel-fortran-rt
18
18
  Requires-Dist: scikit-learn
19
19
  Requires-Dist: cryptography
20
- Requires-Dist: jax
20
+ Requires-Dist: jax ==0.4.30
21
21
  Requires-Dist: triangle
22
- Requires-Dist: numpy
23
- Requires-Dist: pyopengl
22
+ Requires-Dist: numpy ==1.23.*
23
+ Requires-Dist: pyopengl ==3.1.*
24
24
  Requires-Dist: pandas
25
25
  Requires-Dist: geopandas
26
26
  Requires-Dist: scipy
@@ -33,7 +33,7 @@ Requires-Dist: graphviz
33
33
  Requires-Dist: beautifulsoup4
34
34
  Requires-Dist: requests
35
35
  Requires-Dist: notebook
36
- Requires-Dist: matplotlib
36
+ Requires-Dist: matplotlib ==3.6.*
37
37
  Requires-Dist: mkl
38
38
  Requires-Dist: python-gettext
39
39
  Requires-Dist: shapely
@@ -49,10 +49,10 @@ Requires-Dist: python-docx
49
49
  Requires-Dist: pygltflib
50
50
  Requires-Dist: ezdxf
51
51
  Requires-Dist: pyvista
52
- Requires-Dist: tqdm
52
+ Requires-Dist: tqdm ==4.64.*
53
53
  Requires-Dist: osmnx
54
54
  Requires-Dist: tifffile
55
- Requires-Dist: numba
55
+ Requires-Dist: numba ==0.58.*
56
56
  Requires-Dist: xmltodict
57
57
  Requires-Dist: opencv-python
58
58
  Requires-Dist: xarray
@@ -1,3 +1,4 @@
1
+ wolfhece/Coordinates_operations.py,sha256=YyWlAwKManb-ReQrmP37rEXxehunUCihmkeDYX6qTAQ,8037
1
2
  wolfhece/CpGrid.py,sha256=ke4n1khTUoed2asJl1GR25PsEkI4TpiBDCo4u0aSo9M,10658
2
3
  wolfhece/GraphNotebook.py,sha256=V1_Ak4F_hoIpKm2GAakuyKOALhujjIXB5FwzFHtM5-8,28021
3
4
  wolfhece/GraphProfile.py,sha256=OCgJo0YFFBI6H1z-5egJsOOoWF_iziiza0-bbPejNMc,69656
@@ -6,7 +7,7 @@ wolfhece/ManageParams.py,sha256=EeuUI5Vvh9ixCvYf8YShMC1s1Yacc7OxOCN7q81gqiQ,517
6
7
  wolfhece/Model1D.py,sha256=uL1DJVmDI2xVSE7H6n3icn3QbsPtTHeg8E-6wkDloKw,476914
7
8
  wolfhece/PyConfig.py,sha256=FB8u0belXOXTb03Ln6RdVWvMgjzi3oGPCmw2dWa3lNg,8332
8
9
  wolfhece/PyCrosssections.py,sha256=FnmM9DWY_SAF2EDH9Gu2PojXNtSTRF4-aYQuAAJXBh4,112771
9
- wolfhece/PyDraw.py,sha256=mMPowIkidxWrEqQwvsFG__HADLjIu-nVRX0ChllpAUA,390687
10
+ wolfhece/PyDraw.py,sha256=t3U7YQwBA5OYh-Pf0tffZCv9Ha9SH3S-XAhFXVOpeBs,390808
10
11
  wolfhece/PyGui.py,sha256=aRWv9tBpRl7sKEd2gHWj8Bss0ZOKbGlUYIehWHFm8WY,105008
11
12
  wolfhece/PyGuiHydrology.py,sha256=f60E8K9eGTnRq5RDF6yvt-ahf2AYegwQ9t25zZ2Mk1A,14946
12
13
  wolfhece/PyHydrographs.py,sha256=jwtSNMMACwarxrtN1UeQYth99UNrhwPx1IGgUwcooHA,3774
@@ -47,7 +48,7 @@ wolfhece/pywalous.py,sha256=yRaWJjKckXef1d9D5devP0yFHC9uc6kRV4G5x9PNq9k,18972
47
48
  wolfhece/rain_SPWMI.py,sha256=qCfcmF7LajloOaCwnTrrSMzyME03YyilmRUOqrPrv3U,13846
48
49
  wolfhece/textpillow.py,sha256=map7HsGYML_o5NHRdFg2s_TVQed_lDnpYNDv27MM0Vw,14130
49
50
  wolfhece/tools_mpl.py,sha256=gQ3Jg1iuZiecmMqa5Eli2ZLSkttu68VXL8YmMDBaEYU,564
50
- wolfhece/wolf_array.py,sha256=AUSGolqu-YfyQ2SJp5WCMVD1gppvddHULJ3GrUpBjB4,357128
51
+ wolfhece/wolf_array.py,sha256=cWkTaA4nY9I8-FLNlXBwn0xkvaN1EXw6M9NYUYnTqf8,368492
51
52
  wolfhece/wolf_hist.py,sha256=7jeVrgSkM3ErJO6SRMH_PGzfLjIdw8vTy87kesldggk,3582
52
53
  wolfhece/wolf_texture.py,sha256=DS5eobLxrq9ljyebYfpMSQPn8shkUAZZVfqrOKN_QUU,16951
53
54
  wolfhece/wolf_tiles.py,sha256=2Ho2I20rHRY81KXxjgLOYISdF4OkJ2d6omeY4shDoGI,10386
@@ -71,7 +72,7 @@ wolfhece/apps/check_install.py,sha256=SG024u18G7VRLKynbp7DKD1jImtHwuWwN4bJWHm-YH
71
72
  wolfhece/apps/curvedigitizer.py,sha256=_hRR2PWow7PU7rTHIbc6ykZ08tCXcK9uy7RFrb4EKkE,5196
72
73
  wolfhece/apps/isocurrent.py,sha256=MuwTodHxdc6PrqNpphR2ntYf1NLL2n9klTPndGrOHDQ,4109
73
74
  wolfhece/apps/splashscreen.py,sha256=SrustmIQeXnsiD-92OzjdGhBi-S7c_j-cSvuX4T6rtg,2929
74
- wolfhece/apps/version.py,sha256=ls80i8D7bLwHlLa8Q9f7hPR0HGZE2sGy0sqH0i67EBs,388
75
+ wolfhece/apps/version.py,sha256=1j6FkD-S6lKIU8cwsITdvdCE6HZVOxDK9aVtSrdT5Vw,388
75
76
  wolfhece/apps/wolf.py,sha256=mM6Tyi4DlKQILmO49cDUCip9fYVy-hLXkY3YhZgIeUQ,591
76
77
  wolfhece/apps/wolf2D.py,sha256=yPQGee7fsegoQ8GfWKrWEjX1Az_ApL-UWlBiqPvaIyY,565
77
78
  wolfhece/apps/wolf_logo.bmp,sha256=ruJ4MA51CpGO_AYUp_dB4SWKHelvhOvd7Q8NrVOjDJk,3126
@@ -254,7 +255,7 @@ wolfhece/report/reporting.py,sha256=JUEXovx_S4jpYkJEBU0AC-1Qw2OkkWyV3VAp6iOfSHc,
254
255
  wolfhece/report/wolf_report.png,sha256=NoSV58LSwb-oxCcZScRiJno-kxDwRdm_bK-fiMsKJdA,592485
255
256
  wolfhece/scenario/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
256
257
  wolfhece/scenario/check_scenario.py,sha256=w7_SST4n_uec-MUBK36gbJzz2KC8qT_bVJ_VNyp7cMo,4917
257
- wolfhece/scenario/config_manager.py,sha256=k5wPR58AQk_xSN1uzKR0DjGume26NrbBFZvAbgMIhlc,85287
258
+ wolfhece/scenario/config_manager.py,sha256=UzInaAtxmTLqUaXAHg17iLcUZRGTrPPLsj2D9kBs6wI,85468
258
259
  wolfhece/scenario/imposebc_void.py,sha256=PqA_99hKcaqK5zsK6IRIc5Exgg3WVpgWU8xpwNL49zQ,5571
259
260
  wolfhece/scenario/update_void.py,sha256=ay8C_FxfXN627Hx46waaAO6F3ovYmOCTxseUumKAY7c,7474
260
261
  wolfhece/shaders/fragment_shader_texture.glsl,sha256=w6h8d5mJqFaGbao0LGmjRcFFdcEQ3ICIl9JpuT71K5k,177
@@ -277,8 +278,8 @@ wolfhece/ui/wolf_multiselection_collapsiblepane.py,sha256=8PlMYrb_8jI8h9F0_EagpM
277
278
  wolfhece/ui/wolf_times_selection_comparison_models.py,sha256=ORy7fz4dcp691qKzaOZHrRLZ0uXNhL-LIHxmpDGL6BI,5007
278
279
  wolfhece/wintab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
279
280
  wolfhece/wintab/wintab.py,sha256=8A-JNONV6ujgsgG3lM5Uw-pVgglPATwKs86oBzzljoc,7179
280
- wolfhece-2.1.45.dist-info/METADATA,sha256=y63i_CDK02w9EiVXGhd2JlyrM_GCuI0iP7BTQfkmbAw,2488
281
- wolfhece-2.1.45.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
282
- wolfhece-2.1.45.dist-info/entry_points.txt,sha256=Q5JuIWV4odeIJI3qc6fV9MwRoz0ezqPVlFC1Ppm_vdQ,395
283
- wolfhece-2.1.45.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
284
- wolfhece-2.1.45.dist-info/RECORD,,
281
+ wolfhece-2.1.48.dist-info/METADATA,sha256=2S8MGX7bCP41sSY7a9scSmcsfKj2w8uiaMzIY5bIY9k,2548
282
+ wolfhece-2.1.48.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
283
+ wolfhece-2.1.48.dist-info/entry_points.txt,sha256=Q5JuIWV4odeIJI3qc6fV9MwRoz0ezqPVlFC1Ppm_vdQ,395
284
+ wolfhece-2.1.48.dist-info/top_level.txt,sha256=EfqZXMVCn7eILUzx9xsEu2oBbSo9liWPFWjIHik0iCI,9
285
+ wolfhece-2.1.48.dist-info/RECORD,,