1- """Provide functions to read information on raster data from file path or object path
1+ """Provide functions to read information on raster data
22
33The module contains the following class :
44
5- - ' Raster' - Structure describing raster data.
6-
5+ - Raster - Structure describing raster data.
6+ - RasterSet - Structure describing a set of raster data.
77"""
88
9+ import copy
10+ import json
911import re
1012from enum import Enum
13+ from typing import Tuple , Dict
1114
1215from osgeo import ogr , gdal
13- from typing import Tuple
1416
15- from rok4 .Storage import exists , get_osgeo_path
17+ from rok4 .Storage import exists , get_osgeo_path , put_data_str
1618from rok4 .Utils import ColorFormat , compute_bbox ,compute_format
1719
1820# Enable GDAL/OGR exceptions
@@ -23,12 +25,18 @@ class Raster:
2325 """A structure describing raster data
2426
2527 Attributes :
26- path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/path/to/image.tif)
27- bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
28+ path (str): path to the file/object (ex:
29+ file:///path/to/image.tif or s3://bucket/path/to/image.tif)
30+ bbox (Tuple[float, float, float, float]): bounding rectange
31+ in the data projection
2832 bands (int): number of color bands (or channels)
29- format (ColorFormat): numeric variable format for color values. Bit depth, as bits per channel, can be derived from it.
30- 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. ex: file:///path/to/image.msk or s3://bucket/path/to/image.msk)
31- dimensions (Tuple[int, int]): image width and height expressed in pixels
33+ format (ColorFormat): numeric variable format for color values.
34+ Bit depth, as bits per channel, can be derived from it.
35+ mask (str): path to the associated mask file or object, if any,
36+ or None (same path as the image, but with a ".msk" extension
37+ and TIFF format. ex:
38+ file:///path/to/image.msk or s3://bucket/path/to/image.msk)
39+ dimensions (Tuple[int, int]): image width and height, in pixels
3240 """
3341
3442 def __init__ (self ) -> None :
@@ -40,7 +48,7 @@ def __init__(self) -> None:
4048 self .path = None
4149
4250 @classmethod
43- def from_file (cls , path : str ) -> ' Raster' :
51+ def from_file (cls , path : str ) -> " Raster" :
4452 """Creates a Raster object from an image
4553
4654 Args:
@@ -53,7 +61,9 @@ def from_file(cls, path: str) -> 'Raster':
5361 from rok4.Raster import Raster
5462
5563 try:
56- raster = Raster.from_file("file:///data/images/SC1000_TIFF_LAMB93_FXX/SC1000_0040_6150_L93.tif")
64+ raster = Raster.from_file(
65+ "file:///data/SC1000/0040_6150_L93.tif"
66+ )
5767
5868 except Exception as e:
5969 print(f"Cannot load information from image : {e}")
@@ -65,7 +75,6 @@ def from_file(cls, path: str) -> 'Raster':
6575 Returns:
6676 Raster: a Raster instance
6777 """
68-
6978 if not exists (path ):
7079 raise Exception (f"No file or object found at path '{ path } '." )
7180
@@ -76,15 +85,16 @@ def from_file(cls, path: str) -> 'Raster':
7685 image_datasource = gdal .Open (work_image_path )
7786 self .path = path
7887
79- path_pattern = re .compile (' (/[^/]+?)[.][a-zA-Z0-9_-]+$' )
80- mask_path = path_pattern .sub (' \\ 1.msk' , path )
88+ path_pattern = re .compile (" (/[^/]+?)[.][a-zA-Z0-9_-]+$" )
89+ mask_path = path_pattern .sub (" \\ 1.msk" , path )
8190
8291 if exists (mask_path ):
8392 work_mask_path = get_osgeo_path (mask_path )
8493 mask_driver = gdal .IdentifyDriver (work_mask_path ).ShortName
8594 if 'GTiff' != mask_driver :
86- raise Exception (f"Mask file '{ mask_path } ' is not a TIFF image. (GDAL driver : '{ mask_driver } '" )
87-
95+ message = (f"Mask file '{ mask_path } ' is not a TIFF image."
96+ + f" (GDAL driver : '{ mask_driver } '" )
97+ raise Exception (message )
8898 self .mask = mask_path
8999 else :
90100 self .mask = None
@@ -97,36 +107,55 @@ def from_file(cls, path: str) -> 'Raster':
97107 return self
98108
99109 @classmethod
100- def from_parameters (cls , path : str , bands : int , bbox : Tuple [float , float , float , float ], dimensions : Tuple [int , int ], format : ColorFormat , mask : str = None ) -> 'Raster' :
110+ def from_parameters (
111+ cls , path : str , bands : int , bbox : Tuple [float , float , float , float ],
112+ dimensions : Tuple [int , int ], format : ColorFormat , mask : str = None ) -> "Raster" :
101113 """Creates a Raster object from parameters
102114
103115 Args:
104- path (str): path to the file/object (ex: file:///path/to/image.tif or s3://bucket/path/to/image.tif)
116+ path (str): path to the file/object (ex:
117+ file:///path/to/image.tif or s3://bucket/image.tif)
105118 bands (int): number of color bands (or channels)
106- bbox (Tuple[float, float, float, float]): bounding rectange in the data projection
107- dimensions (Tuple[int, int]): image width and height expressed in pixels
108- format (ColorFormat): numeric variable format for color values. Bit depth, as bits per channel, can be derived from it.
109- mask (str, optionnal): 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. ex: file:///path/to/image.msk or s3://bucket/path/to/image.msk)
119+ bbox (Tuple[float, float, float, float]): bounding rectange
120+ in the data projection
121+ dimensions (Tuple[int, int]): image width and height
122+ expressed in pixels
123+ format (ColorFormat): numeric format for color values.
124+ Bit depth, as bits per channel, can be derived from it.
125+ mask (str, optionnal): path to the associated mask, if any,
126+ or None (same path as the image, but with a
127+ ".msk" extension and TIFF format. ex:
128+ file:///path/to/image.msk or s3://bucket/image.msk)
110129
111130 Examples:
112131
113- Loading informations from parameters, related to a TIFF main image coupled to a TIFF mask image
132+ Loading informations from parameters, related to
133+ a TIFF main image coupled to a TIFF mask image
114134
115135 from rok4.Raster import Raster
116136
117137 try:
118- raster = Raster.from_parameters(path="file:///data/images/SC1000_TIFF_LAMB93_FXX/SC1000_0040_6150_L93.tif", mask="file:///data/images/SC1000_TIFF_LAMB93_FXX/SC1000_0040_6150_L93.msk", bands=3, format=ColorFormat.UINT8, dimensions=(2000, 2000), bbox=(40000.000, 5950000.000, 240000.000, 6150000.000))
138+ raster = Raster.from_parameters(
139+ path="file:///data/SC1000/_0040_6150_L93.tif",
140+ mask="file:///data/SC1000/0040_6150_L93.msk",
141+ bands=3,
142+ format=ColorFormat.UINT8,
143+ dimensions=(2000, 2000),
144+ bbox=(40000.000, 5950000.000,
145+ 240000.000, 6150000.000)
146+ )
119147
120148 except Exception as e:
121- print(f"Cannot load information from parameters : {e}")
149+ print(
150+ f"Cannot load information from parameters : {e}"
151+ )
122152
123153 Raises:
124154 KeyError: a mandatory argument is missing
125155
126156 Returns:
127157 Raster: a Raster instance
128158 """
129-
130159 self = cls ()
131160
132161 self .path = path
@@ -135,5 +164,192 @@ def from_parameters(cls, path: str, bands: int, bbox: Tuple[float, float, float,
135164 self .dimensions = dimensions
136165 self .format = format
137166 self .mask = mask
167+ return self
168+
169+
170+ class RasterSet :
171+ """A structure describing a set of raster data
172+
173+ Attributes :
174+ raster_list (List[Raster]): List of Raster instances in the set
175+ colors (List[Dict]): List of color properties for each raster
176+ instance. Contains only one element if
177+ the set is homogenous.
178+ Element properties:
179+ bands (int): number of color bands (or channels)
180+ format (ColorFormat): numeric variable format for
181+ color values. Bit depth, as bits per channel,
182+ can be derived from it.
183+ srs (str): Name of the set's spatial reference system
184+ bbox (Tuple[float, float, float, float]): bounding rectange
185+ in the data projection, enclosing the whole set
186+ """
187+
188+ def __init__ (self ) -> None :
189+ self .bbox = (None , None , None , None )
190+ self .colors = []
191+ self .raster_list = []
192+ self .srs = None
193+
194+ @classmethod
195+ def from_list (cls , path : str , srs : str ) -> "RasterSet" :
196+ """Instanciate a RasterSet from an images list path and a srs
197+
198+ Args:
199+ path (str): path to the images list file or object
200+ (each line in this list contains the path to
201+ an image file or object in the set)
202+
203+ Examples:
204+
205+ Loading informations from a file stored list
206+
207+ from rok4.Raster import RasterSet
208+
209+ try:
210+ raster_set = RasterSet.from_list(
211+ path="file:///data/SC1000.list",
212+ srs="EPSG:3857"
213+ )
214+
215+ except Exception as e:
216+ print(
217+ f"Cannot load information from list file : {e}"
218+ )
219+
220+ Raises:
221+ RuntimeError: raised by OGR/GDAL if anything goes wrong
222+ NotImplementedError: Storage type not handled
223+
224+ Returns:
225+ RasterSet: a RasterSet instance
226+ """
227+ self = cls ()
228+ self .srs = srs
229+
230+ local_list_path = get_osgeo_path (path )
231+ image_list = []
232+ with open (file = local_list_path , mode = "r" ) as list_file :
233+ for line in list_file :
234+ image_path = line .strip (" \t \n \r " )
235+ image_list .append (image_path )
236+
237+ temp_bbox = [None , None , None , None ]
238+ for image_path in image_list :
239+ raster = Raster .from_file (image_path )
240+ self .raster_list .append (raster )
241+ if temp_bbox == [None , None , None , None ]:
242+ for i in range (0 , 4 , 1 ):
243+ temp_bbox [i ] = raster .bbox [i ]
244+ else :
245+ if temp_bbox [0 ] > raster .bbox [0 ]:
246+ temp_bbox [0 ] = raster .bbox [0 ]
247+ if temp_bbox [1 ] > raster .bbox [1 ]:
248+ temp_bbox [1 ] = raster .bbox [1 ]
249+ if temp_bbox [2 ] < raster .bbox [2 ]:
250+ temp_bbox [2 ] = raster .bbox [2 ]
251+ if temp_bbox [3 ] < raster .bbox [3 ]:
252+ temp_bbox [3 ] = raster .bbox [3 ]
253+ color_dict = {"bands" : raster .bands , "format" : raster .format }
254+ if color_dict not in self .colors :
255+ self .colors .append (color_dict )
256+ self .bbox = tuple (temp_bbox )
257+ return self
258+
259+ @classmethod
260+ def from_descriptor (cls , path : str ) -> "RasterSet" :
261+ """Creates a RasterSet object from a descriptor file or object
262+
263+ Args:
264+ path (str): path to the descriptor file or object
265+
266+ Examples:
267+
268+ Loading informations from a file stored descriptor
269+
270+ from rok4.Raster import RasterSet
271+
272+ try:
273+ raster_set = RasterSet.from_descriptor(
274+ "file:///data/images/descriptor.json"
275+ )
276+
277+ except Exception as e:
278+ message = ("Cannot load information from "
279+ + f"descriptor file : {e}")
280+ print(message)
138281
282+ Raises:
283+ RuntimeError: raised by OGR/GDAL if anything goes wrong
284+ NotImplementedError: Storage type not handled
285+
286+ Returns:
287+ RasterSet: a RasterSet instance
288+ """
289+ self = cls ()
290+ descriptor_path = get_osgeo_path (path )
291+ with open (file = descriptor_path , mode = "r" ) as file_handle :
292+ raw_content = file_handle .read ()
293+ serialization = json .loads (raw_content )
294+ self .srs = serialization ["srs" ]
295+ self .raster_list = []
296+ for raster_dict in serialization ["raster_list" ]:
297+ parameters = copy .deepcopy (raster_dict )
298+ parameters ["bbox" ] = tuple (raster_dict ["bbox" ])
299+ parameters ["dimensions" ] = tuple (raster_dict ["dimensions" ])
300+ parameters ["format" ] = ColorFormat [ raster_dict ["format" ] ]
301+ self .raster_list .append (Raster .from_parameters (** parameters ))
302+ self .bbox = tuple (serialization ["bbox" ])
303+ self .colors = []
304+ for color_dict in serialization ["colors" ]:
305+ color_item = copy .deepcopy (color_dict )
306+ color_item ["format" ] = ColorFormat [ color_dict ["format" ] ]
307+ self .colors .append (color_item )
139308 return self
309+
310+ @property
311+ def serializable (self ) -> Dict :
312+ """Get the dict version of the raster set, descriptor compliant
313+
314+ Returns:
315+ Dict: descriptor structured object description
316+ """
317+ serialization = {
318+ "bbox" : list (self .bbox ),
319+ "srs" : self .srs ,
320+ "colors" : [],
321+ "raster_list" : []
322+ }
323+ for color in self .colors :
324+ color_serial = {
325+ "bands" : color ["bands" ],
326+ "format" : color ["format" ].name
327+ }
328+ serialization ["colors" ].append (color_serial )
329+ for raster in self .raster_list :
330+ raster_dict = {
331+ "path" : raster .path ,
332+ "dimensions" : list (raster .dimensions ),
333+ "bbox" : list (raster .bbox ),
334+ "bands" : raster .bands ,
335+ "format" : raster .format .name
336+ }
337+ if raster .mask is not None :
338+ raster_dict ["mask" ] = raster .mask
339+ serialization ["raster_list" ].append (raster_dict )
340+ return serialization
341+
342+ def write_descriptor (self , path : str = None ) -> None :
343+ """Print raster set's descriptor as JSON
344+
345+ Args:
346+ path (str, optional): Complete path (file or object)
347+ where to print the raster set's JSON. Defaults to None,
348+ JSON is printed to standard output.
349+ """
350+ content = json .dumps (self .serializable , sort_keys = True )
351+ if path is None :
352+ print (content )
353+ else :
354+ put_data_str (content , path )
355+
0 commit comments