3333 ColorMapType ,
3434 GDALColorMapType ,
3535 IntervalTuple ,
36+ NoData ,
3637 NumType ,
3738 RIOResampling ,
3839 WarpResampling ,
@@ -159,6 +160,9 @@ class PointData:
159160 crs : Optional [CRS ] = attr .ib (default = None , kw_only = True )
160161 assets : Optional [List ] = attr .ib (default = None , kw_only = True )
161162 metadata : Optional [Dict ] = attr .ib (factory = dict , kw_only = True )
163+ nodata : Optional [NoData ] = attr .ib (default = None , kw_only = True )
164+ scales : Optional [List [NumType ]] = attr .ib (kw_only = True )
165+ offsets : Optional [List [NumType ]] = attr .ib (kw_only = True )
162166 pixel_location : Optional [Tuple [NumType , NumType ]] = attr .ib (
163167 default = None , kw_only = True
164168 )
@@ -179,6 +183,14 @@ def _validate_coordinates(self, attribute, value):
179183 def _default_names (self ):
180184 return [f"b{ ix + 1 } " for ix in range (self .count )]
181185
186+ @scales .default
187+ def _default_scales (self ):
188+ return [1.0 ] * self .count
189+
190+ @offsets .default
191+ def _default_offsets (self ):
192+ return [0.0 ] * self .count
193+
182194 @property
183195 def data (self ) -> numpy .ndarray :
184196 """Return data part of the masked array."""
@@ -236,6 +248,10 @@ def create_from_list(cls, data: Sequence["PointData"]) -> Self:
236248 itertools .chain .from_iterable ([pt .band_names for pt in data if pt .band_names ])
237249 )
238250
251+ scales = list (itertools .chain .from_iterable ([pt .scales for pt in data ]))
252+
253+ offsets = list (itertools .chain .from_iterable ([pt .offsets for pt in data ]))
254+
239255 metadata = dict (
240256 itertools .chain .from_iterable (
241257 [pt .metadata .items () for pt in data if pt .metadata ]
@@ -246,6 +262,8 @@ def create_from_list(cls, data: Sequence["PointData"]) -> Self:
246262 arr ,
247263 assets = assets ,
248264 band_names = band_names ,
265+ offsets = offsets ,
266+ scales = scales ,
249267 coordinates = data [0 ].coordinates ,
250268 crs = data [0 ].crs ,
251269 metadata = metadata ,
@@ -311,6 +329,9 @@ class ImageData:
311329 )
312330 crs : Optional [CRS ] = attr .ib (default = None , kw_only = True )
313331 metadata : Optional [Dict ] = attr .ib (factory = dict , kw_only = True )
332+ nodata : Optional [NoData ] = attr .ib (default = None , kw_only = True )
333+ scales : Optional [List [NumType ]] = attr .ib (kw_only = True )
334+ offsets : Optional [List [NumType ]] = attr .ib (kw_only = True )
314335 band_names : Optional [List [str ]] = attr .ib (kw_only = True )
315336 dataset_statistics : Optional [Sequence [Tuple [float , float ]]] = attr .ib (
316337 default = None , kw_only = True
@@ -322,6 +343,14 @@ class ImageData:
322343 def _default_names (self ):
323344 return [f"b{ ix + 1 } " for ix in range (self .count )]
324345
346+ @scales .default
347+ def _default_scales (self ):
348+ return [1.0 ] * self .count
349+
350+ @offsets .default
351+ def _default_offsets (self ):
352+ return [0.0 ] * self .count
353+
325354 @alpha_mask .validator
326355 def _check_alpha_mask (self , attribute , value ):
327356 """Make sure alpha mask has valid shame and datatype."""
@@ -411,7 +440,14 @@ def from_bytes(cls, data: bytes) -> Self:
411440 array ,
412441 crs = dataset .crs ,
413442 bounds = dataset .bounds ,
443+ band_names = [
444+ dataset .descriptions [ix - 1 ] or f"b{ idx } " for idx in indexes
445+ ],
414446 dataset_statistics = dataset_statistics ,
447+ nodata = dataset .nodata ,
448+ scales = list (dataset .scales ),
449+ offsets = list (dataset .offsets ),
450+ metadata = dataset .tags (),
415451 )
416452
417453 @classmethod
@@ -464,6 +500,10 @@ def create_from_list(cls, data: Sequence["ImageData"]) -> Self:
464500 )
465501 )
466502
503+ scales = list (itertools .chain .from_iterable ([img .scales for img in data ]))
504+
505+ offsets = list (itertools .chain .from_iterable ([img .offsets for img in data ]))
506+
467507 stats = list (
468508 itertools .chain .from_iterable (
469509 [img .dataset_statistics for img in data if img .dataset_statistics ]
@@ -486,6 +526,8 @@ def create_from_list(cls, data: Sequence["ImageData"]) -> Self:
486526 dataset_statistics = dataset_statistics ,
487527 cutline_mask = cutline_mask ,
488528 metadata = metadata ,
529+ scales = scales ,
530+ offsets = offsets ,
489531 )
490532
491533 def data_as_image (self ) -> numpy .ndarray :
@@ -540,6 +582,10 @@ def rescale(
540582 out_range = dtype_ranges [out_dtype ],
541583 ).astype (out_dtype )
542584
585+ # reset scales/offsets
586+ self .scales = [1.0 ] * self .count
587+ self .offsets = [0.0 ] * self .count
588+
543589 return self
544590
545591 def apply_color_formula (self , color_formula : Optional [str ]) -> Self :
@@ -560,6 +606,10 @@ def apply_color_formula(self, color_formula: Optional[str]) -> Self:
560606 self .alpha_mask , in_range = dtype_ranges [str (self .alpha_mask .dtype )]
561607 ).astype ("uint8" )
562608
609+ # reset scales/offsets
610+ self .scales = [1.0 ] * self .count
611+ self .offsets = [0.0 ] * self .count
612+
563613 return self
564614
565615 def apply_colormap (self , colormap : ColorMapType ) -> "ImageData" :
@@ -643,6 +693,9 @@ def resize(
643693 crs = self .crs ,
644694 bounds = self .bounds ,
645695 band_names = self .band_names ,
696+ nodata = self .nodata ,
697+ scales = self .scales ,
698+ offsets = self .offsets ,
646699 metadata = self .metadata ,
647700 dataset_statistics = self .dataset_statistics ,
648701 alpha_mask = alpha_mask ,
@@ -660,6 +713,9 @@ def clip(self, bbox: BBox) -> "ImageData":
660713 crs = self .crs ,
661714 bounds = bbox ,
662715 band_names = self .band_names ,
716+ nodata = self .nodata ,
717+ scales = self .scales ,
718+ offsets = self .offsets ,
663719 metadata = self .metadata ,
664720 dataset_statistics = self .dataset_statistics ,
665721 alpha_mask = self .alpha_mask [row_slice , col_slice ].copy ()
@@ -777,14 +833,15 @@ def to_raster(self, dst_path: str, *, driver: str = "GTIFF", **kwargs: Any) -> N
777833 if "crs" not in kwargs and self .crs :
778834 kwargs .update ({"crs" : self .crs })
779835
780- write_nodata = " nodata" in kwargs
836+ write_nodata = self . nodata is not None
781837 count , height , width = self .array .shape
782838
783839 output_profile = {
784840 "dtype" : self .array .dtype ,
785841 "count" : count if write_nodata else count + 1 ,
786842 "height" : height ,
787843 "width" : width ,
844+ "nodata" : self .nodata ,
788845 }
789846 output_profile .update (kwargs )
790847
@@ -930,6 +987,9 @@ def reproject(
930987 crs = dst_crs ,
931988 bounds = bounds ,
932989 band_names = self .band_names ,
990+ nodata = self .nodata ,
991+ scales = self .scales ,
992+ offsets = self .offsets ,
933993 metadata = self .metadata ,
934994 dataset_statistics = self .dataset_statistics ,
935995 alpha_mask = alpha_mask ,
0 commit comments