22
33The module contains the following class :
44
5- - Raster - Structure describing raster data.
6- - RasterSet - Structure describing a set of raster data.
5+ - ` Raster` - Structure describing raster data.
6+ - ` RasterSet` - Structure describing a set of raster data.
77"""
88
99# -- IMPORTS --
1010
11- # standard library
12- import copy
1311import json
1412import re
13+ import tempfile
14+
15+ # standard library
16+ from copy import deepcopy
17+ from json .decoder import JSONDecodeError
1518from typing import Dict , Tuple
1619
1720# 3rd party
1821from osgeo import gdal , ogr
1922
2023# package
2124from rok4 .enums import ColorFormat
22- from rok4 .storage import exists , get_osgeo_path , put_data_str
25+ from rok4 .storage import (
26+ copy ,
27+ exists ,
28+ get_data_str ,
29+ get_osgeo_path ,
30+ put_data_str ,
31+ remove ,
32+ )
2333from rok4 .utils import compute_bbox , compute_format
2434
2535# -- GLOBALS --
3242class Raster :
3343 """A structure describing raster data
3444
35- Attributes :
36- path (str): path to the file/object (ex:
37- file:///path/to/image.tif or s3://bucket/path/to/image.tif)
38- bbox (Tuple[float, float, float, float]): bounding rectange
39- in the data projection
40- bands (int): number of color bands (or channels)
41- format (ColorFormat): numeric variable format for color values.
42- Bit depth, as bits per channel, can be derived from it.
43- mask (str): path to the associated mask file or object, if any,
44- or None (same path as the image, but with a ".msk" extension
45- and TIFF format. ex:
46- file:///path/to/image.msk or s3://bucket/path/to/image.msk)
45+ Attributes:
46+ path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/path/to/image.tif)
47+ bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
48+ bands (int): number of color bands (or channels) format (ColorFormat). Numeric variable format for color values. Bit depth, as bits per channel,
49+ can be derived from it.
50+ mask (str): path to the associated mask file or object, if any, or None (same path as the image, but with a ".msk" extension and TIFF format.
51+ Ex: file:///path/to/image.msk or s3://bucket/path/to/image.msk)
4752 dimensions (Tuple[int, int]): image width and height, in pixels
4853 """
4954
@@ -77,14 +82,16 @@ def from_file(cls, path: str) -> "Raster":
7782 print(f"Cannot load information from image : {e}")
7883
7984 Raises:
85+ FormatError: MASK file is not a TIFF
8086 RuntimeError: raised by OGR/GDAL if anything goes wrong
8187 NotImplementedError: Storage type not handled
88+ FileNotFoundError: File or object does not exists
8289
8390 Returns:
8491 Raster: a Raster instance
8592 """
8693 if not exists (path ):
87- raise Exception (f"No file or object found at path '{ path } '." )
94+ raise FileNotFoundError (f"No file or object found at path '{ path } '." )
8895
8996 self = cls ()
9097
@@ -100,11 +107,8 @@ def from_file(cls, path: str) -> "Raster":
100107 work_mask_path = get_osgeo_path (mask_path )
101108 mask_driver = gdal .IdentifyDriver (work_mask_path ).ShortName
102109 if "GTiff" != mask_driver :
103- message = (
104- f"Mask file '{ mask_path } ' is not a TIFF image."
105- + f" (GDAL driver : '{ mask_driver } '"
106- )
107- raise Exception (message )
110+ message = f"Mask file '{ mask_path } ' use GDAL driver : '{ mask_driver } '"
111+ raise FormatError ("TIFF" , mask_path , message )
108112 self .mask = mask_path
109113 else :
110114 self .mask = None
@@ -129,19 +133,13 @@ def from_parameters(
129133 """Creates a Raster object from parameters
130134
131135 Args:
132- path (str): path to the file/object (ex:
133- file:///path/to/image.tif or s3://bucket/image.tif)
136+ path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/image.tif)
134137 bands (int): number of color bands (or channels)
135- bbox (Tuple[float, float, float, float]): bounding rectange
136- in the data projection
137- dimensions (Tuple[int, int]): image width and height
138- expressed in pixels
139- format (ColorFormat): numeric format for color values.
140- Bit depth, as bits per channel, can be derived from it.
141- mask (str, optionnal): path to the associated mask, if any,
142- or None (same path as the image, but with a
143- ".msk" extension and TIFF format. ex:
144- file:///path/to/image.msk or s3://bucket/image.msk)
138+ bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
139+ dimensions (Tuple[int, int]): image width and height expressed in pixels
140+ format (ColorFormat): numeric format for color values. Bit depth, as bits per channel, can be derived from it.
141+ mask (str, optionnal): path to the associated mask, if any, or None (same path as the image, but with a ".msk"
142+ extension and TIFF format. ex: file:///path/to/image.msk or s3://bucket/image.msk)
145143
146144 Examples:
147145
@@ -152,13 +150,12 @@ def from_parameters(
152150
153151 try:
154152 raster = Raster.from_parameters(
155- path="file:///data/SC1000/_0040_6150_L93.tif",
156- mask="file:///data/SC1000/0040_6150_L93.msk",
157- bands=3,
158- format=ColorFormat.UINT8,
159- dimensions=(2000, 2000),
160- bbox=(40000.000, 5950000.000,
161- 240000.000, 6150000.000)
153+ path="file:///data/SC1000/_0040_6150_L93.tif",
154+ mask="file:///data/SC1000/0040_6150_L93.msk",
155+ bands=3,
156+ format=ColorFormat.UINT8,
157+ dimensions=(2000, 2000),
158+ bbox=(40000.000, 5950000.000, 240000.000, 6150000.000)
162159 )
163160
164161 except Exception as e:
@@ -186,24 +183,16 @@ def from_parameters(
186183class RasterSet :
187184 """A structure describing a set of raster data
188185
189- Attributes :
186+ Attributes:
190187 raster_list (List[Raster]): List of Raster instances in the set
191- colors (List[Dict]): List of color properties for each raster
192- instance. Contains only one element if
193- the set is homogenous.
194- Element properties:
195- bands (int): number of color bands (or channels)
196- format (ColorFormat): numeric variable format for
197- color values. Bit depth, as bits per channel,
198- can be derived from it.
188+ colors (Set[Tuple[int, ColorFormat]]): Set (distinct values) of color properties (bands and format) found in the raster set.
199189 srs (str): Name of the set's spatial reference system
200- bbox (Tuple[float, float, float, float]): bounding rectange
201- in the data projection, enclosing the whole set
190+ bbox (Tuple[float, float, float, float]): bounding rectange in the data projection, enclosing the whole set
202191 """
203192
204193 def __init__ (self ) -> None :
205194 self .bbox = (None , None , None , None )
206- self .colors = []
195+ self .colors = set ()
207196 self .raster_list = []
208197 self .srs = None
209198
@@ -212,9 +201,8 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
212201 """Instanciate a RasterSet from an images list path and a srs
213202
214203 Args:
215- path (str): path to the images list file or object
216- (each line in this list contains the path to
217- an image file or object in the set)
204+ path (str): path to the images list file or object (each line in this list contains the path to an image file or object in the set)
205+ srs (str): images' coordinates system
218206
219207 Examples:
220208
@@ -224,13 +212,13 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
224212
225213 try:
226214 raster_set = RasterSet.from_list(
227- path="file:///data/SC1000.list",
228- srs="EPSG:3857"
215+ path="file:///data/SC1000.list",
216+ srs="EPSG:3857"
229217 )
230218
231219 except Exception as e:
232220 print(
233- f"Cannot load information from list file : {e}"
221+ f"Cannot load information from list file : {e}"
234222 )
235223
236224 Raises:
@@ -243,33 +231,42 @@ def from_list(cls, path: str, srs: str) -> "RasterSet":
243231 self = cls ()
244232 self .srs = srs
245233
246- local_list_path = get_osgeo_path (path )
234+ # Chargement de la liste des images (la liste peut être un fichier ou un objet)
235+ list_obj = tempfile .NamedTemporaryFile (mode = "r" , delete = False )
236+ list_file = list_obj .name
237+ copy (path , f"file://{ list_file } " )
238+ list_obj .close ()
247239 image_list = []
248- with open (file = local_list_path ) as list_file :
249- for line in list_file :
240+ with open (list_file ) as listin :
241+ for line in listin :
250242 image_path = line .strip (" \t \n \r " )
251243 image_list .append (image_path )
252244
253- temp_bbox = [None , None , None , None ]
245+ remove (f"file://{ list_file } " )
246+
247+ bbox = [None , None , None , None ]
254248 for image_path in image_list :
255249 raster = Raster .from_file (image_path )
256250 self .raster_list .append (raster )
257- if temp_bbox == [None , None , None , None ]:
258- for i in range (0 , 4 , 1 ):
259- temp_bbox [i ] = raster .bbox [i ]
251+
252+ # Mise à jour de la bbox globale
253+ if bbox == [None , None , None , None ]:
254+ bbox = list (raster .bbox )
260255 else :
261- if temp_bbox [0 ] > raster .bbox [0 ]:
262- temp_bbox [0 ] = raster .bbox [0 ]
263- if temp_bbox [1 ] > raster .bbox [1 ]:
264- temp_bbox [1 ] = raster .bbox [1 ]
265- if temp_bbox [2 ] < raster .bbox [2 ]:
266- temp_bbox [2 ] = raster .bbox [2 ]
267- if temp_bbox [3 ] < raster .bbox [3 ]:
268- temp_bbox [3 ] = raster .bbox [3 ]
269- color_dict = {"bands" : raster .bands , "format" : raster .format }
270- if color_dict not in self .colors :
271- self .colors .append (color_dict )
272- self .bbox = tuple (temp_bbox )
256+ if bbox [0 ] > raster .bbox [0 ]:
257+ bbox [0 ] = raster .bbox [0 ]
258+ if bbox [1 ] > raster .bbox [1 ]:
259+ bbox [1 ] = raster .bbox [1 ]
260+ if bbox [2 ] < raster .bbox [2 ]:
261+ bbox [2 ] = raster .bbox [2 ]
262+ if bbox [3 ] < raster .bbox [3 ]:
263+ bbox [3 ] = raster .bbox [3 ]
264+
265+ # Inventaire des colors distinctes
266+ self .colors .add ((raster .bands , raster .format ))
267+
268+ self .bbox = tuple (bbox )
269+
273270 return self
274271
275272 @classmethod
@@ -287,12 +284,11 @@ def from_descriptor(cls, path: str) -> "RasterSet":
287284
288285 try:
289286 raster_set = RasterSet.from_descriptor(
290- "file:///data/images/descriptor.json"
287+ "file:///data/images/descriptor.json"
291288 )
292289
293290 except Exception as e:
294- message = ("Cannot load information from "
295- + f"descriptor file : {e}")
291+ message = ("Cannot load information from descriptor file : {e}")
296292 print(message)
297293
298294 Raises:
@@ -303,24 +299,26 @@ def from_descriptor(cls, path: str) -> "RasterSet":
303299 RasterSet: a RasterSet instance
304300 """
305301 self = cls ()
306- descriptor_path = get_osgeo_path (path )
307- with open (file = descriptor_path ) as file_handle :
308- raw_content = file_handle .read ()
309- serialization = json .loads (raw_content )
302+
303+ try :
304+ serialization = json .loads (get_data_str (path ))
305+
306+ except JSONDecodeError as e :
307+ raise FormatError ("JSON" , path , e )
308+
310309 self .srs = serialization ["srs" ]
311310 self .raster_list = []
312311 for raster_dict in serialization ["raster_list" ]:
313- parameters = copy . deepcopy (raster_dict )
312+ parameters = deepcopy (raster_dict )
314313 parameters ["bbox" ] = tuple (raster_dict ["bbox" ])
315314 parameters ["dimensions" ] = tuple (raster_dict ["dimensions" ])
316315 parameters ["format" ] = ColorFormat [raster_dict ["format" ]]
317316 self .raster_list .append (Raster .from_parameters (** parameters ))
317+
318318 self .bbox = tuple (serialization ["bbox" ])
319- self .colors = []
320319 for color_dict in serialization ["colors" ]:
321- color_item = copy .deepcopy (color_dict )
322- color_item ["format" ] = ColorFormat [color_dict ["format" ]]
323- self .colors .append (color_item )
320+ self .colors .add ((color_dict ["bands" ], ColorFormat [color_dict ["format" ]]))
321+
324322 return self
325323
326324 @property
@@ -332,7 +330,7 @@ def serializable(self) -> Dict:
332330 """
333331 serialization = {"bbox" : list (self .bbox ), "srs" : self .srs , "colors" : [], "raster_list" : []}
334332 for color in self .colors :
335- color_serial = {"bands" : color ["bands" ], "format" : color ["format" ].name }
333+ color_serial = {"bands" : color [0 ], "format" : color [1 ].name }
336334 serialization ["colors" ].append (color_serial )
337335 for raster in self .raster_list :
338336 raster_dict = {
@@ -345,15 +343,14 @@ def serializable(self) -> Dict:
345343 if raster .mask is not None :
346344 raster_dict ["mask" ] = raster .mask
347345 serialization ["raster_list" ].append (raster_dict )
346+
348347 return serialization
349348
350349 def write_descriptor (self , path : str = None ) -> None :
351350 """Print raster set's descriptor as JSON
352351
353352 Args:
354- path (str, optional): Complete path (file or object)
355- where to print the raster set's JSON. Defaults to None,
356- JSON is printed to standard output.
353+ path (str, optional): Complete path (file or object) where to print the raster set's JSON. Defaults to None, JSON is printed to standard output.
357354 """
358355 content = json .dumps (self .serializable , sort_keys = True )
359356 if path is None :
0 commit comments