From 9e64be3dd15244a59610ef26e964e6574c7515bc Mon Sep 17 00:00:00 2001 From: Brian Smith <14266876+brianhendee@users.noreply.github.com> Date: Mon, 5 May 2025 18:14:12 +0000 Subject: [PATCH 01/30] Optimize memory management in file write --- sarpy/io/complex/sicd.py | 7 +++++-- sarpy/io/general/nitf.py | 13 ++++++++++--- sarpy/io/product/sidd.py | 7 +++++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/sarpy/io/complex/sicd.py b/sarpy/io/complex/sicd.py index ccda743c..8ba9def9 100644 --- a/sarpy/io/complex/sicd.py +++ b/sarpy/io/complex/sicd.py @@ -865,7 +865,8 @@ def __init__( sicd_meta: Optional[SICDType] = None, sicd_writing_details: Optional[SICDWritingDetails] = None, check_older_version: bool = False, - check_existence: bool = True): + check_existence: bool = True, + in_memory: bool = None): """ Parameters @@ -878,6 +879,8 @@ def __init__( NGA applications like SOCET or RemoteView check_existence : bool Should we check if the given file already exists? + in_memory : bool + If True force in-memory writing, if False force file writing. """ if sicd_meta is None and sicd_writing_details is None: @@ -885,7 +888,7 @@ def __init__( if sicd_writing_details is None: sicd_writing_details = SICDWritingDetails(sicd_meta, check_older_version=check_older_version) NITFWriter.__init__( - self, file_object, sicd_writing_details, check_existence=check_existence) + self, file_object, sicd_writing_details, check_existence=check_existence, in_memory=in_memory) @property def nitf_writing_details(self) -> SICDWritingDetails: diff --git a/sarpy/io/general/nitf.py b/sarpy/io/general/nitf.py index 86fa75f9..7a2ada6e 100644 --- a/sarpy/io/general/nitf.py +++ b/sarpy/io/general/nitf.py @@ -2535,6 +2535,8 @@ def _flatten_bytes(value: Union[bytes, Sequence]) -> bytes: return value elif isinstance(value, Sequence): return b''.join(_flatten_bytes(entry) for entry in value) + elif isinstance(value, numpy.ndarray) and value.dtype == numpy.uint8: + return value.reshape(-1) else: raise TypeError('input must be a bytes object, or a sequence with bytes objects as leaves') @@ -3516,7 +3518,8 @@ def __init__( self, file_object: Union[str, BinaryIO], writing_details: NITFWritingDetails, - check_existence: bool = True): + check_existence: bool = True, + in_memory: bool = None): """ Parameters @@ -3525,6 +3528,8 @@ def __init__( writing_details : NITFWritingDetails check_existence : bool Should we check if the given file already exists? + in_memory : bool + If True force in-memory writing, if False force file writing. Raises ------ @@ -3547,9 +3552,11 @@ def __init__( raise ValueError('file_object requires a file path or BinaryIO object') self._file_object = file_object - if is_real_file(file_object): + if in_memory is not None: + self._in_memory = in_memory + elif is_real_file(file_object): self._file_name = file_object.name - self._in_memory = False + self._in_memory = True else: self._file_name = None self._in_memory = True diff --git a/sarpy/io/product/sidd.py b/sarpy/io/product/sidd.py index b57c5972..68da3064 100644 --- a/sarpy/io/product/sidd.py +++ b/sarpy/io/product/sidd.py @@ -893,7 +893,8 @@ def __init__( Sequence[SIDDType2], Sequence[SIDDType1]]] = None, sicd_meta: Optional[Union[SICDType, Sequence[SICDType]]] = None, sidd_writing_details: Optional[SIDDWritingDetails] = None, - check_existence: bool = True): + check_existence: bool = True, + in_memory: bool = False): """ Parameters @@ -904,6 +905,8 @@ def __init__( sidd_writing_details : None|SIDDWritingDetails check_existence : bool Should we check if the given file already exists? + in_memory : bool + If True force in-memory writing, if False force file writing. """ if sidd_meta is None and sidd_writing_details is None: @@ -911,7 +914,7 @@ def __init__( if sidd_writing_details is None: sidd_writing_details = SIDDWritingDetails(sidd_meta, sicd_meta=sicd_meta) NITFWriter.__init__( - self, file_object, sidd_writing_details, check_existence=check_existence) + self, file_object, sidd_writing_details, check_existence=check_existence, in_memory=in_memory) @property def nitf_writing_details(self) -> SIDDWritingDetails: From 57ab9129bb8ded39a97d66aaa89c7314f637f21b Mon Sep 17 00:00:00 2001 From: Brian Smith <14266876+brianhendee@users.noreply.github.com> Date: Wed, 7 May 2025 15:14:48 +0000 Subject: [PATCH 02/30] Allow memory management to be turned on --- sarpy/io/general/data_segment.py | 2 +- sarpy/io/general/nitf.py | 10 ++++++---- sarpy/io/product/sidd.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sarpy/io/general/data_segment.py b/sarpy/io/general/data_segment.py index 60dd3a4d..6e8980c1 100644 --- a/sarpy/io/general/data_segment.py +++ b/sarpy/io/general/data_segment.py @@ -2053,7 +2053,7 @@ def get_raw_bytes(self, warn: bool = False) -> Union[bytes, Tuple]: logger.error( 'There has been a call to `get_raw_bytes` from {},\n\t' 'but all pixels are not fully written'.format(self.__class__)) - return self.underlying_array.tobytes() + return self.underlying_array.view('B').reshape(-1) def flush(self) -> None: self._validate_closed() diff --git a/sarpy/io/general/nitf.py b/sarpy/io/general/nitf.py index 7a2ada6e..d9b7443c 100644 --- a/sarpy/io/general/nitf.py +++ b/sarpy/io/general/nitf.py @@ -3552,15 +3552,17 @@ def __init__( raise ValueError('file_object requires a file path or BinaryIO object') self._file_object = file_object - if in_memory is not None: - self._in_memory = in_memory - elif is_real_file(file_object): + + if is_real_file(file_object): self._file_name = file_object.name - self._in_memory = True + self._in_memory = False else: self._file_name = None self._in_memory = True + if in_memory is not None: + self._in_memory = in_memory + self.nitf_writing_details = writing_details if not self.nitf_writing_details.verify_images_have_no_compression(): diff --git a/sarpy/io/product/sidd.py b/sarpy/io/product/sidd.py index 68da3064..41026e63 100644 --- a/sarpy/io/product/sidd.py +++ b/sarpy/io/product/sidd.py @@ -894,7 +894,7 @@ def __init__( sicd_meta: Optional[Union[SICDType, Sequence[SICDType]]] = None, sidd_writing_details: Optional[SIDDWritingDetails] = None, check_existence: bool = True, - in_memory: bool = False): + in_memory: bool = None): """ Parameters From a0b81886212bbaa4fc3a102350b614a77cdc6b5d Mon Sep 17 00:00:00 2001 From: petersontex Date: Tue, 17 Jun 2025 17:38:27 -0400 Subject: [PATCH 03/30] Update setup.py Move required packages from extras_require to install_requires --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5f19f1ae..492500de 100644 --- a/setup.py +++ b/setup.py @@ -62,11 +62,11 @@ def my_test_suite(): url=parameters['__url__'], author=parameters['__author__'], author_email=parameters['__email__'], # The primary POC - install_requires=['numpy>=1.19.0', 'scipy'], + install_requires=['numpy>=1.19.0', 'pillow', 'scipy', 'matplotlib', 'shapely>=1.6.4', 'lxml>=4.1.1'], zip_safe=False, # Use of __file__ and __path__ in some code makes it unusable from zip test_suite="setup.my_test_suite", extras_require={ - "all": ['pillow', 'lxml>=4.1.1', 'matplotlib', 'h5py', 'smart_open[http]', 'pytest>=3.3.2', 'networkx>=2.5', 'shapely>=1.6.4'], + "all": ['h5py', 'smart_open[http]', 'pytest>=3.3.2', 'networkx>=2.5'], }, classifiers=[ 'Development Status :: 4 - Beta', From 078bf76c5d7f12b9703fddd4b72eff2baa28106c Mon Sep 17 00:00:00 2001 From: petersontex Date: Fri, 20 Jun 2025 12:59:56 -0400 Subject: [PATCH 04/30] Update dependencies in setup.py 02 move 'h5py' from extras_require to install_requires --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 492500de..da1c7e29 100644 --- a/setup.py +++ b/setup.py @@ -62,11 +62,11 @@ def my_test_suite(): url=parameters['__url__'], author=parameters['__author__'], author_email=parameters['__email__'], # The primary POC - install_requires=['numpy>=1.19.0', 'pillow', 'scipy', 'matplotlib', 'shapely>=1.6.4', 'lxml>=4.1.1'], + install_requires=['h5py', 'numpy>=1.19.0', 'pillow', 'scipy', 'matplotlib', 'shapely>=1.6.4', 'lxml>=4.1.1'], zip_safe=False, # Use of __file__ and __path__ in some code makes it unusable from zip test_suite="setup.my_test_suite", extras_require={ - "all": ['h5py', 'smart_open[http]', 'pytest>=3.3.2', 'networkx>=2.5'], + "all": ['smart_open[http]', 'pytest>=3.3.2', 'networkx>=2.5'], }, classifiers=[ 'Development Status :: 4 - Beta', From 05c4ca93b0d6a45e5d6d1710ea0f2e5165c5e024 Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Fri, 20 Jun 2025 15:13:46 -0400 Subject: [PATCH 05/30] First in memory write test attempt --- tests/io/general/test_nitf.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/io/general/test_nitf.py b/tests/io/general/test_nitf.py index 19f03921..efd72aba 100644 --- a/tests/io/general/test_nitf.py +++ b/tests/io/general/test_nitf.py @@ -58,3 +58,24 @@ def test_write_filehandle(tests_path, tmp_path): writer.write(data) assert not fd.closed + assert filecmp.cmp(in_nitf, out_nitf, shallow=False) + +def test_write_filehandle(tests_path, tmp_path): + in_nitf_mem = tests_path / "data/iq.nitf" + with sarpy.io.general.nitf.NITFReader(str(in_nitf_mem)) as reader_mem: + data_mem = reader_mem.read() + writer_details_mem = sarpy.io.general.nitf.NITFWritingDetails( + reader_mem.nitf_details.nitf_header, + (sarpy.io.general.nitf.ImageSubheaderManager(reader_mem.get_image_header(0)),), + reader_mem.image_segment_collections, + ) + + out_nitf_mem = tmp_path / 'output_memory.nitf' + with out_nitf_mem.open('wb') as fd_mem: + with sarpy.io.general.nitf.NITFWriter( + fd_mem, writing_details=writer_details_mem, in_memory=True + ) as writer_mem: + writer_mem.write(data_mem) + + assert not fd_mem.closed + assert filecmp.cmp(in_nitf_mem, out_nitf_mem, shallow=False) \ No newline at end of file From c1aa88fd4e6f0362fd0ef023d96f43f25b92fb80 Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Fri, 20 Jun 2025 15:17:16 -0400 Subject: [PATCH 06/30] First in memory write test attempt 02 --- tests/io/general/test_nitf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/io/general/test_nitf.py b/tests/io/general/test_nitf.py index efd72aba..9f7fefa5 100644 --- a/tests/io/general/test_nitf.py +++ b/tests/io/general/test_nitf.py @@ -60,7 +60,7 @@ def test_write_filehandle(tests_path, tmp_path): assert not fd.closed assert filecmp.cmp(in_nitf, out_nitf, shallow=False) -def test_write_filehandle(tests_path, tmp_path): +def test_in_memory_write(tests_path, tmp_path): in_nitf_mem = tests_path / "data/iq.nitf" with sarpy.io.general.nitf.NITFReader(str(in_nitf_mem)) as reader_mem: data_mem = reader_mem.read() From 9b071a346904682bd8533f4cbec84461ca8a90e4 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 26 Jun 2025 16:35:25 +0000 Subject: [PATCH 07/30] Update sidd_product_creation.py add remapu function to create_sidd_structure call for 16 bit remap work --- sarpy/processing/sidd/sidd_product_creation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sarpy/processing/sidd/sidd_product_creation.py b/sarpy/processing/sidd/sidd_product_creation.py index 88a93756..716a0d1f 100644 --- a/sarpy/processing/sidd/sidd_product_creation.py +++ b/sarpy/processing/sidd/sidd_product_creation.py @@ -43,7 +43,7 @@ DEFAULT_IMG_REMAP = NRL DEFAULT_CSI_REMAP = NRL -DEFAULT_DI_REMAP = NRL +DEFAULT_DI_REMAP = NRL _output_text = 'output_directory `{}`\n\t' \ 'does not exist or is not a directory' @@ -167,7 +167,7 @@ def create_detected_image_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Detected Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), version=version) + product_class='Detected Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), remap_function=remap_function, version=version) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = ortho_helper.sicd.get_suggested_name(ortho_helper.index)+'_IMG' From 61da4cb76db413c9b15e201ac1a1823dc86a3d97 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 26 Jun 2025 17:22:18 +0000 Subject: [PATCH 08/30] Update sidd_structure_creation.py add the remap function to the signature and call for create_sidd_structures --- .../processing/sidd/sidd_structure_creation.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sarpy/processing/sidd/sidd_structure_creation.py b/sarpy/processing/sidd/sidd_structure_creation.py index 79dda752..1f839517 100644 --- a/sarpy/processing/sidd/sidd_structure_creation.py +++ b/sarpy/processing/sidd/sidd_structure_creation.py @@ -209,7 +209,7 @@ def _create_plane_projection_v3(proj_helper, bounds): ######################### # Version 3 element creation -def create_sidd_structure_v3(ortho_helper, bounds, product_class, pixel_type): +def create_sidd_structure_v3(ortho_helper, bounds, product_class, pixel_type, remap_function=None ): """ Create a SIDD version 3.0 structure based on the orthorectification helper and pixel bounds. @@ -244,9 +244,9 @@ def _create_display_v3(): NonInteractiveProcessing=[NonInteractiveProcessingType3( ProductGenerationOptions=ProductGenerationOptionsType3( DataRemapping=NewLookupTableType3( - LUTName='DENSITY', + LUTName=remap_function.name.upper(), Predefined=PredefinedLookupType3( - DatabaseName='DENSITY'))), + DatabaseName=remap_function.name.upper()))), RRDS=RRDSType3(DownsamplingMethod='DECIMATE'), band=i+1) for i in range(bands)], InteractiveProcessing=[InteractiveProcessingType3( @@ -326,7 +326,7 @@ def _create_exploitation_v3(): ######################### # Version 2 element creation -def create_sidd_structure_v2(ortho_helper, bounds, product_class, pixel_type): +def create_sidd_structure_v2(ortho_helper, bounds, product_class, pixel_type, remap_function=None ): """ Create a SIDD version 2.0 structure based on the orthorectification helper and pixel bounds. @@ -361,9 +361,9 @@ def _create_display_v2(): NonInteractiveProcessing=[NonInteractiveProcessingType2( ProductGenerationOptions=ProductGenerationOptionsType2( DataRemapping=NewLookupTableType( - LUTName='DENSITY', + LUTName=remap_function.name.upper(), Predefined=PredefinedLookupType( - DatabaseName='DENSITY'))), + DatabaseName=remap_function.name.upper()))), RRDS=RRDSType2(DownsamplingMethod='DECIMATE'), band=i+1) for i in range(bands)], InteractiveProcessing=[InteractiveProcessingType2( @@ -521,7 +521,7 @@ def _create_exploitation_v1(): ########################## # Switchable version SIDD structure -def create_sidd_structure(ortho_helper, bounds, product_class, pixel_type, version=3): +def create_sidd_structure(ortho_helper, bounds, product_class, pixel_type, remap_function=None, version=3): """ Create a SIDD structure, with version specified, based on the orthorectification helper and pixel bounds. @@ -550,6 +550,6 @@ def create_sidd_structure(ortho_helper, bounds, product_class, pixel_type, versi if version == 1: return create_sidd_structure_v1(ortho_helper, bounds, product_class, pixel_type) elif version == 2: - return create_sidd_structure_v2(ortho_helper, bounds, product_class, pixel_type) + return create_sidd_structure_v2(ortho_helper, bounds, product_class, pixel_type, remap_function=remap_function ) else: - return create_sidd_structure_v3(ortho_helper, bounds, product_class, pixel_type) + return create_sidd_structure_v3(ortho_helper, bounds, product_class, pixel_type, remap_function=remap_function ) From c05d8c44ba380238267472b07093aaf47f025669 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 26 Jun 2025 17:27:28 +0000 Subject: [PATCH 09/30] Update create_product.py add bit depth to create_product and to the create_detected_image for 16bit remap work --- sarpy/utils/create_product.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sarpy/utils/create_product.py b/sarpy/utils/create_product.py index 0724eeb9..e3336198 100644 --- a/sarpy/utils/create_product.py +++ b/sarpy/utils/create_product.py @@ -55,6 +55,9 @@ def main(args=None): parser.add_argument( '-m', '--method', default='nearest', choices=['nearest', ]+['spline_{}'.format(i) for i in range(1, 6)], help="The interpolation method.") + parser.add_argument( + '-b', '--bit_depth', default='8', choices=['8', '16' ], + help="SIDD product pixel bit depth.") parser.add_argument( '--version', default=2, type=int, choices=[1, 2, 3], help="The version of the SIDD standard used.") @@ -80,7 +83,7 @@ def main(args=None): ortho_helper = NearestNeighborMethod(reader, index=i) if args.type == 'detected': create_detected_image_sidd(ortho_helper, args.output_directory, - remap_function=remap.get_registered_remap(args.remap), + remap_function=remap.get_registered_remap(args.remap, bit_depth=args.bit_depth ), version=args.version, include_sicd=args.sicd) elif args.type == 'csi': create_csi_sidd(ortho_helper, args.output_directory, From 89ee214fe3564aa314245a3260ee012ee473b3ce Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 26 Jun 2025 18:30:29 +0000 Subject: [PATCH 10/30] Update remap.py changes for 16 bit remap --- sarpy/visualization/remap.py | 61 +++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index ab0158b5..26b972f2 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -33,6 +33,7 @@ class based solution which allows state variables associated with the remap _DEFAULTS_REGISTERED = False _REMAP_DICT = OrderedDict() # type: Dict[str, RemapFunction] +newRegMap = OrderedDict() ########### # helper functions @@ -1846,14 +1847,17 @@ def _register_defaults(): global _DEFAULTS_REGISTERED if _DEFAULTS_REGISTERED: return - register_remap(NRL(bit_depth=8), overwrite=False) - register_remap(Density(bit_depth=8), overwrite=False) - register_remap(High_Contrast(bit_depth=8), overwrite=False) - register_remap(Brighter(bit_depth=8), overwrite=False) - register_remap(Darker(bit_depth=8), overwrite=False) - register_remap(Linear(bit_depth=8), overwrite=False) - register_remap(Logarithmic(bit_depth=8), overwrite=False) - register_remap(PEDF(bit_depth=8), overwrite=False) + + # register class as opposed to instance of class that it was + register_remap(NRL, overwrite=False ) + register_remap(Linear, overwrite=False ) + register_remap(Density, overwrite=False ) + register_remap(High_Contrast, overwrite=False ) + register_remap(Brighter, overwrite=False ) + register_remap(Darker, overwrite=False ) + register_remap(Logarithmic, overwrite=False ) + register_remap(PEDF, overwrite=False ) + if plt is not None: try: register_remap(LUT8bit(NRL(bit_depth=8), 'viridis', use_alpha=False), overwrite=False) @@ -1871,6 +1875,32 @@ def _register_defaults(): register_remap(LUT8bit(NRL(bit_depth=8), 'bone', use_alpha=False), overwrite=False) except KeyError: pass + # joz + newRegMap[ 'nrl' ] = NRL + newRegMap[ 'linear' ] = Linear + newRegMap[ 'density' ] = Density + newRegMap[ 'high_contrast' ] = High_Contrast + newRegMap[ 'brighter' ] = Brighter + newRegMap[ 'darker' ] = Darker + newRegMap[ 'log' ] = Logarithmic + newRegMap[ 'pedf' ] = PEDF + if plt is not None: + try: + newRegMap[ 'viridis' ] = LUT8bit(NRL(bit_depth=8), 'viridis', use_alpha=False) + except KeyError: + pass + try: + newRegMap[ 'magma' ] = LUT8bit(NRL(bit_depth=8), 'magma', use_alpha=False) + except KeyError: + pass + try: + newRegMap[ 'rainbow' ] = LUT8bit(NRL(bit_depth=8), 'rainbow', use_alpha=False) + except KeyError: + pass + try: + newRegMap[ 'bone' ] = LUT8bit(NRL(bit_depth=8), 'bone', use_alpha=False) + except KeyError: + pass _DEFAULTS_REGISTERED = True @@ -1886,7 +1916,7 @@ def get_remap_names() -> List[str]: if not _DEFAULTS_REGISTERED: _register_defaults() - return list(_REMAP_DICT.keys()) + return list(newRegMap.keys()) def get_remap_list() -> List[Tuple[str, RemapFunction]]: @@ -1910,9 +1940,12 @@ def get_remap_list() -> List[Tuple[str, RemapFunction]]: def get_registered_remap( remap_name: str, + bit_depth=8, default: Optional[RemapFunction] = None) -> RemapFunction: """ - Gets a remap function from it's registered name. + Gets a remap Class/constructor/init from it's registered name. + # add 16 bit ability by newRegMap is dict of class/constructors + Parameters ---------- @@ -1921,7 +1954,7 @@ def get_registered_remap( Returns ------- - RemapFunction + RemapFunction Class Raises ------ @@ -1930,6 +1963,12 @@ def get_registered_remap( if not _DEFAULTS_REGISTERED: _register_defaults() + + if remap_name in newRegMap: + # Try new regerst map return class/Constructor + myFunc = newRegMap[ remap_name ] + newRemap = myFunc( remap_name, bit_depth) + return newRemap if remap_name in _REMAP_DICT: return _REMAP_DICT[remap_name] From 466fbd17a49d2bdd9a21bb5630bc525ad3e6c710 Mon Sep 17 00:00:00 2001 From: petersontex Date: Fri, 27 Jun 2025 14:52:03 -0400 Subject: [PATCH 11/30] Update __about__.py Changed __version__ assignment and changed __email__ to a team email distribution. --- sarpy/__about__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sarpy/__about__.py b/sarpy/__about__.py index 5a196794..7da772c7 100644 --- a/sarpy/__about__.py +++ b/sarpy/__about__.py @@ -27,13 +27,12 @@ '__license__', '__copyright__'] from sarpy.__details__ import __classification__, _post_identifier -_version_number = '1.3.60' -__version__ = _version_number + _post_identifier +__version__ = "1.3.61" __author__ = "National Geospatial-Intelligence Agency" __url__ = "https://github.com/ngageoint/sarpy" -__email__ = "richard.m.naething@nga.mil" +__email__ = "SARToolboxDev@nga.mil" __title__ = "sarpy" From 7877e7b0f4df50ac5bb6319717ab82a366b13747 Mon Sep 17 00:00:00 2001 From: petersontex Date: Fri, 27 Jun 2025 14:54:08 -0400 Subject: [PATCH 12/30] Create build.yml Add automation for releases and release candidates. --- .github/workflows/build.yml | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..8a8522e2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,96 @@ +name: Build packages + +on: + push: + branches: + - master + - 'integration/**' + +jobs: + build: + # This section builds the distribution for all versions of Python listed in + # the python-version matrix under strategy to confirm that it builds for + # all of those versions. It then uploads the package built by Python 3.9 + # for use in later jobs. + name: Build distribution + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract_version.outputs.raw_version }} + rc_version: ${{ steps.extract_release_candidate_version.outputs.version }} + strategy: + matrix: + python-version: [3.9, 3.10, 3.11, 3.12, 3.13, 3.14] + steps: + # Pull the current branch to build the package from. + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{github.ref}} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Extract version number from __about__.py + shell: bash + run: echo "raw_version=`sed -n 's/__version__ = \"\(.*\)\"/\1/p' < sarpy/__about__.py`" >> $GITHUB_OUTPUT + id: extract_version + - name: Set version number for release candidate + shell: bash + if: contains(github.ref, 'refs/heads/integration/') + run: | + echo ${{ steps.extract_version.outputs.raw_version }} + rc_version=${{ steps.extract_version.outputs.raw_version }}rc0 + echo "version=$rc_version" >> $GITHUB_OUTPUT + id: extract_release_candidate_version + - name: Install pypa/build + run: >- + python3 -m + pip install + build + --user + - name: Build sarpy binary wheel and a source tarball + run: python3 -m build + - name: Upload all the dists + if: matrix.python-version == 3.9 + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.extract_version.outputs.raw_version }} + path: dist/ + overwrite: true + # This job creates a GitHub release and uploads the package contents created + # in the build job to the release. + release: + name: Create release + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{github.ref}} + - name: Extract version number from __about__.py + shell: bash + run: | + if "${{endswith(github.ref, 'master')}}" + then + echo "version=${{ needs.build.outputs.version }}" >> $GITHUB_OUTPUT + else + echo "version=${{ needs.build.outputs.rc_version }}" >> $GITHUB_OUTPUT + fi + id: extract_version + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build.outputs.version }} + path: dist + - name: Create a release + id: create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.extract_version.outputs.version }} + generate_release_notes: true + name: Version ${{ steps.extract_version.outputs.version }} + draft: false + prerelease: false + target_commitish: ${{github.ref}} + files: dist/* From 84ed587939318286513da66cce9fceedb513b31e Mon Sep 17 00:00:00 2001 From: jonlorax Date: Wed, 2 Jul 2025 21:10:40 +0000 Subject: [PATCH 13/30] Update sidd_product_creation.py add remap function into create_sidd_structure --- sarpy/processing/sidd/sidd_product_creation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sarpy/processing/sidd/sidd_product_creation.py b/sarpy/processing/sidd/sidd_product_creation.py index 716a0d1f..fb6d3e49 100644 --- a/sarpy/processing/sidd/sidd_product_creation.py +++ b/sarpy/processing/sidd/sidd_product_creation.py @@ -256,7 +256,7 @@ def create_csi_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Color Subaperture Image', pixel_type='RGB24I', version=version) + product_class='Color Subaperture Image', pixel_type='RGB24I', remap_function=remap_function, version=version) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = csi_calculator.sicd.get_suggested_name(csi_calculator.index)+'_CSI' @@ -352,7 +352,7 @@ def create_dynamic_image_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Dynamic Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), version=version) + product_class='Dynamic Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), remap_function=remap_function, version=version) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = subap_calculator.sicd.get_suggested_name(subap_calculator.index)+'__DI' the_sidds = [] From ddcb86c2007c3f6ce400d8d58464051aa0152690 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Wed, 2 Jul 2025 21:24:14 +0000 Subject: [PATCH 14/30] Update remap.py update remape after pytest work this was tricky on the github web page --- sarpy/visualization/remap.py | 70 ++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index 26b972f2..b3c20df5 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -1828,11 +1828,17 @@ def register_remap( """ if isinstance(remap_function, type) and issubclass(remap_function, RemapFunction): - remap_function = remap_function() + remap_function = remap_function( bit_depth=bit_depth) if not isinstance(remap_function, RemapFunction): raise TypeError('remap_function must be an instance of RemapFunction.') remap_name = remap_function.name + if remap_function.bit_depth == 16: # joz + rm_name = remap_name + '_' + str( remap_function.bit_depth ) # joz + else: + rm_name = remap_name + remap_name = rm_name + if remap_name not in _REMAP_DICT: _REMAP_DICT[remap_name] = remap_function @@ -1849,14 +1855,24 @@ def _register_defaults(): return # register class as opposed to instance of class that it was - register_remap(NRL, overwrite=False ) - register_remap(Linear, overwrite=False ) - register_remap(Density, overwrite=False ) - register_remap(High_Contrast, overwrite=False ) - register_remap(Brighter, overwrite=False ) - register_remap(Darker, overwrite=False ) - register_remap(Logarithmic, overwrite=False ) - register_remap(PEDF, overwrite=False ) + register_remap(NRL( bit_depth=8), overwrite=False) + register_remap(Density( bit_depth=8), overwrite=False) + register_remap(High_Contrast( bit_depth=8), overwrite=False) + register_remap(Brighter( bit_depth=8), overwrite=False) + register_remap(Darker( bit_depth=8), overwrite=False) + register_remap(Linear( bit_depth=8), overwrite=False) + register_remap(Logarithmic( bit_depth=8), overwrite=False) + register_remap(PEDF( bit_depth=8), overwrite=False) + + register_remap(NRL( bit_depth=16), overwrite=False) + register_remap(Density( bit_depth=16), overwrite=False) + register_remap(High_Contrast( bit_depth=16), overwrite=False) + register_remap(Brighter( bit_depth=16), overwrite=False) + register_remap(Darker( bit_depth=16), overwrite=False) + register_remap(Linear( bit_depth=16), overwrite=False) + register_remap(Logarithmic( bit_depth=16), overwrite=False) + register_remap(PEDF( bit_depth=16), overwrite=False) + if plt is not None: try: @@ -1875,32 +1891,6 @@ def _register_defaults(): register_remap(LUT8bit(NRL(bit_depth=8), 'bone', use_alpha=False), overwrite=False) except KeyError: pass - # joz - newRegMap[ 'nrl' ] = NRL - newRegMap[ 'linear' ] = Linear - newRegMap[ 'density' ] = Density - newRegMap[ 'high_contrast' ] = High_Contrast - newRegMap[ 'brighter' ] = Brighter - newRegMap[ 'darker' ] = Darker - newRegMap[ 'log' ] = Logarithmic - newRegMap[ 'pedf' ] = PEDF - if plt is not None: - try: - newRegMap[ 'viridis' ] = LUT8bit(NRL(bit_depth=8), 'viridis', use_alpha=False) - except KeyError: - pass - try: - newRegMap[ 'magma' ] = LUT8bit(NRL(bit_depth=8), 'magma', use_alpha=False) - except KeyError: - pass - try: - newRegMap[ 'rainbow' ] = LUT8bit(NRL(bit_depth=8), 'rainbow', use_alpha=False) - except KeyError: - pass - try: - newRegMap[ 'bone' ] = LUT8bit(NRL(bit_depth=8), 'bone', use_alpha=False) - except KeyError: - pass _DEFAULTS_REGISTERED = True @@ -1916,7 +1906,7 @@ def get_remap_names() -> List[str]: if not _DEFAULTS_REGISTERED: _register_defaults() - return list(newRegMap.keys()) + return list( _REMAP_DICT.keys()) def get_remap_list() -> List[Tuple[str, RemapFunction]]: @@ -1972,6 +1962,14 @@ def get_registered_remap( if remap_name in _REMAP_DICT: return _REMAP_DICT[remap_name] + if int( bit_depth ) == 16: # joz + rm_name = remap_name + '_' + str( bit_depth ) # joz + else: + rm_name = remap_name + + if rm_name in _REMAP_DICT: + return _REMAP_DICT[ rm_name ] + if default is not None: return default raise KeyError('Unregistered remap name `{}`'.format(remap_name)) From 96945ee52f1e6cb5591388aa3c2da304e8e8fab3 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Wed, 2 Jul 2025 21:27:02 +0000 Subject: [PATCH 15/30] Update test_remap.py fixing test for changing signature for bit depth option --- tests/visualization/test_remap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/visualization/test_remap.py b/tests/visualization/test_remap.py index 2a23d641..7961590c 100644 --- a/tests/visualization/test_remap.py +++ b/tests/visualization/test_remap.py @@ -329,7 +329,7 @@ def test_remap_names(self): def test_get_registered_remap(self): with self.assertRaises(KeyError): remap.get_registered_remap("__fake__") - self.assertEqual(remap.get_registered_remap("__fake__", "default"), "default") + self.assertEqual(remap.get_registered_remap("__fake__", 8, "default"), "default") def test_get_remap_list(self): remap_list = remap.get_remap_list() From b94303db0a1493a3b53d786f720d40fc534e8d91 Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Fri, 4 Jul 2025 12:43:08 -0400 Subject: [PATCH 16/30] Added testing for SICDWriter. --- tests/io/complex/test_sicd.py | 257 +++++++++++++++++++++++++++++++++- 1 file changed, 252 insertions(+), 5 deletions(-) diff --git a/tests/io/complex/test_sicd.py b/tests/io/complex/test_sicd.py index 788bdade..d20442d0 100644 --- a/tests/io/complex/test_sicd.py +++ b/tests/io/complex/test_sicd.py @@ -1,10 +1,13 @@ -import os import json +import numpy +import os +import pytest import tempfile import unittest -from sarpy.io.complex.converter import conversion_utility -from sarpy.io.complex.sicd import SICDReader +from sarpy.io.complex.converter import conversion_utility, open_complex +from sarpy.io.complex.sicd import SICDReader, SICDWriter, SICDWritingDetails +from sarpy.io.complex.sicd_elements.SICD import SICDType from sarpy.io.complex.sicd_schema import get_schema_path, get_default_version_string @@ -35,12 +38,256 @@ the_version = get_default_version_string() the_schema = get_schema_path(the_version) +def test_sicd_writer_init_failure_no_input(tmp_path): + with pytest.raises(TypeError, + match="missing 1 required positional argument: 'file_object'"): + sicd_writer = SICDWriter() + +def test_sicd_writer_init_failure_file_only(tmp_path): + output_file = tmp_path / "out.sicd" + with pytest.raises(ValueError, + match="One of sicd_meta or sicd_writing_details must be provided."): + sicd_writer = SICDWriter(output_file) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_meta_only(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + with pytest.raises(TypeError, + match="missing 1 required positional argument: 'file_object'"): + sicd_writer = SICDWriter(sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_writing_details_only(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + with pytest.raises(TypeError, + match="missing 1 required positional argument: 'file_object'"): + sicd_writer = SICDWriter(sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_meta(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_writing_details(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_meta_invalid_output(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + output_file = str("/does_not_exist/out.sicd") + with pytest.raises(FileNotFoundError, + match="No such file or directory: '/does_not_exist/out.sicd'"): + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_writing_details_invalid_output(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str("/does_not_exist/out.sicd") + with pytest.raises(FileNotFoundError, + match="No such file or directory: '/does_not_exist/out.sicd'"): + sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_bad_sicd_meta(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_meta_bad_string = str(sicd_meta) + "})" + sicd_meta_bad_type = SICDType(sicd_meta_bad_string) + output_file = str(tmp_path / "out.sicd") + with pytest.raises(ValueError, + match="The sicd_meta has un-populated ImageData, and nothing useful can be inferred."): + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta_bad_type) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_bad_sicd_writing_details(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + # sicd_meta_bad_string = str(sicd_meta) + "})" + # sicd_meta_bad_type = SICDType(sicd_meta_bad_string) + sicd_writing_details_bad = "Bad Data" + output_file = str(tmp_path / "out.sicd") + with pytest.raises(TypeError, + match="nitf_writing_details must be of type "): + sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details_bad) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_nitf_writing_details(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) + assert sicd_writer.nitf_writing_details == sicd_writing_details + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_nitf_writing_details_setter(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) + with pytest.raises(ValueError, + match="nitf_writing_details is read-only"): + sicd_writer.nitf_writing_details = sicd_writing_details + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_get_format_function(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, + complex_order='IQ', lut=numpy.array([1, 2])) + + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_get_format_function_bad_band_type(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + with pytest.raises(ValueError, + match="Got unsupported SICD band type definition"): + sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, + complex_order='Steve', lut=numpy.array([1, 2])) + + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_meta(tmp_path): + input_file = sicd_files[0] + reader = open_complex(input_file) + sicd_meta = reader.sicd_meta + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + # Must confirm each piece of the SICDType is equal because there isn't an + # == operator for SICDType. + # First confirm that all CollectionInfo properties are equal. + assert sicd_meta.CollectionInfo.Classification == \ + sicd_writer.sicd_meta.CollectionInfo.Classification + assert sicd_meta.CollectionInfo.CollectorName == \ + sicd_writer.sicd_meta.CollectionInfo.CollectorName + assert sicd_meta.CollectionInfo.IlluminatorName == \ + sicd_writer.sicd_meta.CollectionInfo.IlluminatorName + assert sicd_meta.CollectionInfo.CoreName == \ + sicd_writer.sicd_meta.CollectionInfo.CoreName + assert sicd_meta.CollectionInfo.CollectType == \ + sicd_writer.sicd_meta.CollectionInfo.CollectType + assert sicd_meta.CollectionInfo.CountryCodes == \ + sicd_writer.sicd_meta.CollectionInfo.CountryCodes + assert sicd_meta.CollectionInfo.Parameters == \ + sicd_writer.sicd_meta.CollectionInfo.Parameters + assert sicd_meta.CollectionInfo.RadarMode.ModeType == \ + sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeType + assert sicd_meta.CollectionInfo.RadarMode.ModeID == \ + sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeID + assert sicd_meta.ImageCreation.Application == \ + sicd_writer.sicd_meta.ImageCreation.Application + assert sicd_meta.ImageCreation.DateTime == \ + sicd_writer.sicd_meta.ImageCreation.DateTime + assert sicd_meta.ImageCreation.Site == \ + sicd_writer.sicd_meta.ImageCreation.Site + # TODO: These are different because one is based on the profile from the + # file and the other is based on the profile of the version of sarpy + # we are testing. Skip for now. Determine if these should be the same + # assert sicd_meta.ImageCreation.Profile == \ + # sicd_writer.sicd_meta.ImageCreation.Profile + assert sicd_meta.ImageData.AmpTable == \ + sicd_writer.sicd_meta.ImageData.AmpTable + assert sicd_meta.ImageData.FirstCol == \ + sicd_writer.sicd_meta.ImageData.FirstCol + assert sicd_meta.ImageData.FirstRow == \ + sicd_writer.sicd_meta.ImageData.FirstRow + assert sicd_meta.ImageData.PixelType == \ + sicd_writer.sicd_meta.ImageData.PixelType + assert sicd_meta.ImageData.SCPPixel.Row == \ + sicd_writer.sicd_meta.ImageData.SCPPixel.Row + assert sicd_meta.ImageData.SCPPixel.Col == \ + sicd_writer.sicd_meta.ImageData.SCPPixel.Col + assert sicd_meta.ImageData.FullImage.NumCols == \ + sicd_writer.sicd_meta.ImageData.FullImage.NumCols + assert sicd_meta.ImageData.FullImage.NumRows == \ + sicd_writer.sicd_meta.ImageData.FullImage.NumRows + assert sicd_meta.GeoData.EarthModel == \ + sicd_writer.sicd_meta.GeoData.EarthModel + assert sicd_meta.GeoData.SCP.ECF.X == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.X + assert sicd_meta.GeoData.SCP.ECF.Y == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.Y + assert sicd_meta.GeoData.SCP.ECF.Z == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.Z + assert sicd_meta.GeoData.SCP.LLH.Lat == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.Lat + assert sicd_meta.GeoData.SCP.LLH.Lon == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.Lon + assert sicd_meta.GeoData.SCP.LLH.HAE == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.HAE + # TODO: Confirm ImageCorners match. + # assert sicd_meta.GeoData.ImageCorners.FRFC == \ + # sicd_writer.sicd_meta.GeoData.ImageCorners.FRFC + # assert sicd_meta.GeoData.ImageCorners.FRLC == \ + # sicd_writer.sicd_meta.GeoData.ImageCorners.FRLC + # assert sicd_meta.GeoData.ImageCorners.LRFC == \ + # sicd_writer.sicd_meta.GeoData.ImageCorners.LRFC + # assert sicd_meta.GeoData.ImageCorners.LRLC == \ + # sicd_writer.sicd_meta.GeoData.ImageCorners.LRLC + assert sicd_meta.Grid.Col.SS == \ + sicd_writer.sicd_meta.Grid.Col.SS + assert sicd_meta.Grid.Col.UVectECF.X == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.X + assert sicd_meta.Grid.Col.UVectECF.Y == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.Y + assert sicd_meta.Grid.Col.UVectECF.Z == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.Z + assert sicd_meta.Grid.Row.SS == \ + sicd_writer.sicd_meta.Grid.Row.SS + assert sicd_meta.Grid.Row.UVectECF.X == \ + sicd_writer.sicd_meta.Grid.Row.UVectECF.X + assert sicd_meta.Grid.Row.UVectECF.Y == \ + sicd_writer.sicd_meta.Grid.Row.UVectECF.Y + assert sicd_meta.Grid.Row.UVectECF.Z == \ + sicd_writer.sicd_meta.Grid.Row.UVectECF.Z + # assert sicd_meta.Grid == \ + # sicd_writer.sicd_meta.Grid + + # 'Grid', 'Timeline', 'Position', + # 'RadarCollection', 'ImageFormation', 'SCPCOA', 'Radiometric', 'Antenna', 'ErrorStatistics', + # 'MatchInfo', 'RgAzComp', 'PFA', 'RMA' class TestSICDWriting(unittest.TestCase): - @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') + def setUp(self): + self.sicd_files = complex_file_types.get('SICD', []) + if len(self.sicd_files) == 0 : + self.skipTest('No sicd files found') + def test_sicd_creation(self): - for fil in sicd_files: + for fil in self.sicd_files: reader = SICDReader(fil) # check that sicd structure serializes according to the schema From 9732028afe9d2c72c89027345174d7c778742b1b Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Fri, 4 Jul 2025 15:30:37 -0400 Subject: [PATCH 17/30] Incremental improvement to SICDWriter tests and added __eq__ to Poly1DType --- sarpy/io/complex/sicd_elements/blocks.py | 6 +++ tests/io/complex/test_sicd.py | 66 ++++++++++++++++++++---- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/sarpy/io/complex/sicd_elements/blocks.py b/sarpy/io/complex/sicd_elements/blocks.py index 33d04bad..0299776c 100644 --- a/sarpy/io/complex/sicd_elements/blocks.py +++ b/sarpy/io/complex/sicd_elements/blocks.py @@ -1090,6 +1090,12 @@ def get_array(self, dtype=numpy.float64) -> numpy.ndarray: """ return numpy.array(self._coefs, dtype=dtype) + + def __eq__(self, other): + if not isinstance(other, Poly1DType): + return TypeError('Provided object is not an Poly1DType.') + return numpy.array_equal(self.Coefs, other.Coefs) and self.order1 == other.order1 + @classmethod def from_node(cls, node, xml_ns, ns_key=None, kwargs=None): diff --git a/tests/io/complex/test_sicd.py b/tests/io/complex/test_sicd.py index d20442d0..a4b99879 100644 --- a/tests/io/complex/test_sicd.py +++ b/tests/io/complex/test_sicd.py @@ -266,17 +266,62 @@ def test_sicd_meta(tmp_path): sicd_writer.sicd_meta.Grid.Col.UVectECF.Z assert sicd_meta.Grid.Row.SS == \ sicd_writer.sicd_meta.Grid.Row.SS - assert sicd_meta.Grid.Row.UVectECF.X == \ - sicd_writer.sicd_meta.Grid.Row.UVectECF.X - assert sicd_meta.Grid.Row.UVectECF.Y == \ - sicd_writer.sicd_meta.Grid.Row.UVectECF.Y - assert sicd_meta.Grid.Row.UVectECF.Z == \ - sicd_writer.sicd_meta.Grid.Row.UVectECF.Z - # assert sicd_meta.Grid == \ - # sicd_writer.sicd_meta.Grid + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.X) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.X) + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Y) + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Z) + assert sicd_meta.Grid.TimeCOAPoly.Coefs == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.Coefs + assert sicd_meta.Grid.TimeCOAPoly.order1 == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.order1 + assert sicd_meta.Grid.TimeCOAPoly.order2 == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.order2 + assert sicd_meta.Grid.Type == \ + sicd_writer.sicd_meta.Grid.Type + assert sicd_meta.Grid.ImagePlane == \ + sicd_writer.sicd_meta.Grid.ImagePlane + assert sicd_meta.Timeline.IPP == \ + sicd_writer.sicd_meta.Timeline.IPP + assert sicd_meta.Timeline.CollectStart == \ + sicd_writer.sicd_meta.Timeline.CollectStart + assert sicd_meta.Timeline.CollectDuration == \ + sicd_writer.sicd_meta.Timeline.CollectDuration + assert pytest.approx(sicd_meta.Position.ARPPoly.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.X) + assert pytest.approx(sicd_meta.Position.ARPPoly.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Y) + assert pytest.approx(sicd_meta.Position.ARPPoly.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Z) + assert pytest.approx(sicd_meta.Position.GRPPoly.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.X) + assert pytest.approx(sicd_meta.Position.GRPPoly.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Y) + assert pytest.approx(sicd_meta.Position.GRPPoly.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Z) + ## TxAPCPoly is empty in test data. + # TODO: Make a __eq__ operator that handles NoneType + # assert pytest.approx(sicd_meta.Position.TxAPCPoly.X) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.X) + # assert pytest.approx(sicd_meta.Position.TxAPCPoly.Y) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Y) + # assert pytest.approx(sicd_meta.Position.TxAPCPoly.Z) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Z) + ## TxAPCPoly is empty in test data. + # TODO: Make a __eq__ operator that handles NoneType + # assert pytest.approx(sicd_meta.Position.RcvAPC.X) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.X) + # assert pytest.approx(sicd_meta.Position.RcvAPC.Y) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Y) + # assert pytest.approx(sicd_meta.Position.RcvAPC.Z) == \ + # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Z) + assert numpy.allclose(sicd_meta.RadarCollection.TxFrequency.get_array(), \ + sicd_writer.sicd_meta.RadarCollection.TxFrequency.get_array()) + assert sicd_meta.RadarCollection.RefFreqIndex == \ + sicd_writer.sicd_meta.RadarCollection.RefFreqIndex - # 'Grid', 'Timeline', 'Position', - # 'RadarCollection', 'ImageFormation', 'SCPCOA', 'Radiometric', 'Antenna', 'ErrorStatistics', + # 'ImageFormation', 'SCPCOA', 'Radiometric', 'Antenna', 'ErrorStatistics', # 'MatchInfo', 'RgAzComp', 'PFA', 'RMA' class TestSICDWriting(unittest.TestCase): @@ -294,6 +339,7 @@ def test_sicd_creation(self): if etree is not None: sicd = reader.get_sicds_as_tuple()[0] xml_doc = etree.fromstring(sicd.to_xml_bytes()) + print(xml_doc) xml_schema = etree.XMLSchema(file=the_schema) with self.subTest(msg='validate xml produced from sicd structure'): self.assertTrue(xml_schema.validate(xml_doc), From f6b2ac62af8ff295d4affb6a1a942bc02059cdc3 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Mon, 7 Jul 2025 17:42:45 +0000 Subject: [PATCH 18/30] Update remap.py Clean up remap get regerstered logic --- sarpy/visualization/remap.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index b3c20df5..33efb9c4 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -1953,15 +1953,7 @@ def get_registered_remap( if not _DEFAULTS_REGISTERED: _register_defaults() - - if remap_name in newRegMap: - # Try new regerst map return class/Constructor - myFunc = newRegMap[ remap_name ] - newRemap = myFunc( remap_name, bit_depth) - return newRemap - - if remap_name in _REMAP_DICT: - return _REMAP_DICT[remap_name] + if int( bit_depth ) == 16: # joz rm_name = remap_name + '_' + str( bit_depth ) # joz else: From 6bb5a8b9b4a2c8972e803af59d03f1e7db00dee5 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Mon, 7 Jul 2025 17:49:02 +0000 Subject: [PATCH 19/30] Update remap.py clean up dead dict --- sarpy/visualization/remap.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index 33efb9c4..25ca76c5 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -33,7 +33,6 @@ class based solution which allows state variables associated with the remap _DEFAULTS_REGISTERED = False _REMAP_DICT = OrderedDict() # type: Dict[str, RemapFunction] -newRegMap = OrderedDict() ########### # helper functions From 174c2a384cdebed4facda9f92c8cca5030587bf0 Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Tue, 8 Jul 2025 10:25:47 -0400 Subject: [PATCH 20/30] Updated sicdwriter tests. --- tests/io/complex/test_sicd.py | 57 +++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/tests/io/complex/test_sicd.py b/tests/io/complex/test_sicd.py index a4b99879..259dc285 100644 --- a/tests/io/complex/test_sicd.py +++ b/tests/io/complex/test_sicd.py @@ -320,9 +320,62 @@ def test_sicd_meta(tmp_path): sicd_writer.sicd_meta.RadarCollection.TxFrequency.get_array()) assert sicd_meta.RadarCollection.RefFreqIndex == \ sicd_writer.sicd_meta.RadarCollection.RefFreqIndex + # TODO: Make this comparison work. + # assert numpy.allclose(sicd_meta.RadarCollection.Waveform.get_array(), \ + # sicd_writer.sicd_meta.RadarCollection.Waveform.get_array()) + assert sicd_meta.RadarCollection.TxPolarization == \ + sicd_writer.sicd_meta.RadarCollection.TxPolarization - # 'ImageFormation', 'SCPCOA', 'Radiometric', 'Antenna', 'ErrorStatistics', - # 'MatchInfo', 'RgAzComp', 'PFA', 'RMA' + # TODO: Make tests for ImageFormation. This type requires an in depth + # comparison + # assert sicd_meta.ImageFormation.TxPolarization == \ + # sicd_writer.sicd_meta.ImageFormation.TxPolarization + + assert pytest.approx(sicd_meta.SCPCOA.SCPTime) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SCPTime) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Z) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Z) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Z) + assert sicd_meta.SCPCOA.SideOfTrack == \ + sicd_writer.sicd_meta.SCPCOA.SideOfTrack + assert pytest.approx(sicd_meta.SCPCOA.SlantRange) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlantRange) + assert pytest.approx(sicd_meta.SCPCOA.GroundRange) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.GroundRange) + assert pytest.approx(sicd_meta.SCPCOA.DopplerConeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.DopplerConeAng) + assert pytest.approx(sicd_meta.SCPCOA.GrazeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.GrazeAng) + assert pytest.approx(sicd_meta.SCPCOA.IncidenceAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.IncidenceAng) + assert pytest.approx(sicd_meta.SCPCOA.TwistAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.TwistAng) + assert pytest.approx(sicd_meta.SCPCOA.SlopeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlopeAng) + assert pytest.approx(sicd_meta.SCPCOA.AzimAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.AzimAng) + assert pytest.approx(sicd_meta.SCPCOA.LayoverAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.LayoverAng) + + # The following sicd_meta features are optional, so skip them in the test for + # now. + # 'Radiometric', 'Antenna', 'ErrorStatistics', 'MatchInfo', 'RgAzComp', + # 'PFA', 'RMA' class TestSICDWriting(unittest.TestCase): From e52a26cdc0a0b0002b9ef7ccce548938cbb5dc56 Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Tue, 8 Jul 2025 12:13:01 -0400 Subject: [PATCH 21/30] Updated sicdwriter tests' names. --- tests/io/complex/test_sicd.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/io/complex/test_sicd.py b/tests/io/complex/test_sicd.py index 259dc285..e9ae203e 100644 --- a/tests/io/complex/test_sicd.py +++ b/tests/io/complex/test_sicd.py @@ -50,7 +50,7 @@ def test_sicd_writer_init_failure_file_only(tmp_path): sicd_writer = SICDWriter(output_file) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_meta_only(tmp_path): +def test_sicd_writer_init_failure_sicd_meta_only(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -59,7 +59,7 @@ def test_sicd_writer_init_sicd_meta_only(tmp_path): sicd_writer = SICDWriter(sicd_meta=sicd_meta) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_writing_details_only(tmp_path): +def test_sicd_writer_init_failure_sicd_writing_details_only(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -87,7 +87,7 @@ def test_sicd_writer_init_sicd_writing_details(tmp_path): sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_meta_invalid_output(tmp_path): +def test_sicd_writer_init_failure_sicd_meta_invalid_output(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -97,7 +97,7 @@ def test_sicd_writer_init_sicd_meta_invalid_output(tmp_path): sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_writing_details_invalid_output(tmp_path): +def test_sicd_writer_init_failure_sicd_writing_details_invalid_output(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -108,7 +108,7 @@ def test_sicd_writer_init_sicd_writing_details_invalid_output(tmp_path): sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_bad_sicd_meta(tmp_path): +def test_sicd_writer_init_failure_bad_sicd_meta(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -120,7 +120,7 @@ def test_sicd_writer_init_bad_sicd_meta(tmp_path): sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta_bad_type) @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_bad_sicd_writing_details(tmp_path): +def test_sicd_writer_init_failure_bad_sicd_writing_details(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -143,7 +143,7 @@ def test_nitf_writing_details(tmp_path): assert sicd_writer.nitf_writing_details == sicd_writing_details @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_nitf_writing_details_setter(tmp_path): +def test_nitf_writing_details_setter_failure(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta @@ -166,7 +166,7 @@ def test_get_format_function(tmp_path): @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_get_format_function_bad_band_type(tmp_path): +def test_get_format_function_failure_bad_band_type(tmp_path): input_file = sicd_files[0] reader = open_complex(input_file) sicd_meta = reader.sicd_meta From d1b9df4d59d591252a526dbe3584d4085bbbdef9 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Tue, 8 Jul 2025 21:15:48 +0000 Subject: [PATCH 22/30] Update sidd_product_creation.py addjust parameter ordering in calls to sidd_product_create --- sarpy/processing/sidd/sidd_product_creation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sarpy/processing/sidd/sidd_product_creation.py b/sarpy/processing/sidd/sidd_product_creation.py index fb6d3e49..82be2c90 100644 --- a/sarpy/processing/sidd/sidd_product_creation.py +++ b/sarpy/processing/sidd/sidd_product_creation.py @@ -167,7 +167,7 @@ def create_detected_image_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Detected Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), remap_function=remap_function, version=version) + product_class='Detected Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), version=version, remap_function=remap_function) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = ortho_helper.sicd.get_suggested_name(ortho_helper.index)+'_IMG' @@ -256,7 +256,7 @@ def create_csi_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Color Subaperture Image', pixel_type='RGB24I', remap_function=remap_function, version=version) + product_class='Color Subaperture Image', pixel_type='RGB24I', version=version, remap_function=remap_function) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = csi_calculator.sicd.get_suggested_name(csi_calculator.index)+'_CSI' @@ -352,7 +352,7 @@ def create_dynamic_image_sidd( ortho_bounds = ortho_iterator.ortho_bounds sidd_structure = create_sidd_structure( ortho_helper, ortho_bounds, - product_class='Dynamic Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), remap_function=remap_function, version=version) + product_class='Dynamic Image', pixel_type='MONO{}I'.format(remap_function.bit_depth), version=version, remap_function=remap_function) # set suggested name sidd_structure.NITF['SUGGESTED_NAME'] = subap_calculator.sicd.get_suggested_name(subap_calculator.index)+'__DI' the_sidds = [] From 0d60bc96b3512c5458cde8e6782e5eebbe545d67 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Tue, 8 Jul 2025 21:24:56 +0000 Subject: [PATCH 23/30] Update sidd_structure_creation.py update parameter order for create_sidd_structure as mentioned in second code review for 16 bit work --- sarpy/processing/sidd/sidd_structure_creation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sarpy/processing/sidd/sidd_structure_creation.py b/sarpy/processing/sidd/sidd_structure_creation.py index 1f839517..7041b858 100644 --- a/sarpy/processing/sidd/sidd_structure_creation.py +++ b/sarpy/processing/sidd/sidd_structure_creation.py @@ -521,7 +521,7 @@ def _create_exploitation_v1(): ########################## # Switchable version SIDD structure -def create_sidd_structure(ortho_helper, bounds, product_class, pixel_type, remap_function=None, version=3): +def create_sidd_structure(ortho_helper, bounds, product_class, pixel_type, version=3, remap_function=None): """ Create a SIDD structure, with version specified, based on the orthorectification helper and pixel bounds. From 310bb100a42caff356dbfa98e058aef8a4bc2108 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Tue, 8 Jul 2025 21:31:18 +0000 Subject: [PATCH 24/30] Update remap.py some joz clean up and parameter order adjustmest from second code review for 16 bit work --- sarpy/visualization/remap.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index 25ca76c5..54b36b8d 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -1832,8 +1832,8 @@ def register_remap( raise TypeError('remap_function must be an instance of RemapFunction.') remap_name = remap_function.name - if remap_function.bit_depth == 16: # joz - rm_name = remap_name + '_' + str( remap_function.bit_depth ) # joz + if remap_function.bit_depth == 16: + rm_name = remap_name + '_' + str( remap_function.bit_depth ) else: rm_name = remap_name remap_name = rm_name @@ -1853,7 +1853,7 @@ def _register_defaults(): if _DEFAULTS_REGISTERED: return - # register class as opposed to instance of class that it was + # register instance of class register_remap(NRL( bit_depth=8), overwrite=False) register_remap(Density( bit_depth=8), overwrite=False) register_remap(High_Contrast( bit_depth=8), overwrite=False) @@ -1929,10 +1929,10 @@ def get_remap_list() -> List[Tuple[str, RemapFunction]]: def get_registered_remap( remap_name: str, - bit_depth=8, - default: Optional[RemapFunction] = None) -> RemapFunction: + default: Optional[RemapFunction] = None, + bit_depth=8) -> RemapFunction: """ - Gets a remap Class/constructor/init from it's registered name. + Gets a remap instance via its registered name. # add 16 bit ability by newRegMap is dict of class/constructors @@ -1953,8 +1953,8 @@ def get_registered_remap( if not _DEFAULTS_REGISTERED: _register_defaults() - if int( bit_depth ) == 16: # joz - rm_name = remap_name + '_' + str( bit_depth ) # joz + if int( bit_depth ) == 16: + rm_name = remap_name + '_' + str( bit_depth ) else: rm_name = remap_name From 1b6925c2dd79ba0598663babcf8e49c65f12f0de Mon Sep 17 00:00:00 2001 From: Peterson <529965@bah.com> Date: Tue, 8 Jul 2025 20:15:09 -0400 Subject: [PATCH 25/30] Moved SICDWriter tests to individual test file. --- tests/io/complex/test_SICDWriter.py | 371 ++++++++++++++++++++++++++++ tests/io/complex/test_sicd.py | 358 +-------------------------- 2 files changed, 377 insertions(+), 352 deletions(-) create mode 100644 tests/io/complex/test_SICDWriter.py diff --git a/tests/io/complex/test_SICDWriter.py b/tests/io/complex/test_SICDWriter.py new file mode 100644 index 00000000..4b86f4fe --- /dev/null +++ b/tests/io/complex/test_SICDWriter.py @@ -0,0 +1,371 @@ +import json +import numpy +import os +import pytest +import unittest + +from sarpy.io.complex.converter import conversion_utility, open_complex +from sarpy.io.complex.sicd import SICDWriter, SICDWritingDetails +from sarpy.io.complex.sicd_elements.SICD import SICDType +from sarpy.io.complex.sicd_schema import get_schema_path, \ + get_default_version_string +from sarpy.io.complex.sicd_elements.blocks import XYZPolyType + +from tests import parse_file_entry + +complex_file_types = {} +this_loc = os.path.abspath(__file__) +# specifies file locations +file_reference = os.path.join(os.path.split(this_loc)[0], \ + 'complex_file_types.json') +if os.path.isfile(file_reference): + with open(file_reference, 'r') as local_file: + test_files_list = json.load(local_file) + for test_files_type in test_files_list: + valid_entries = [] + for entry in test_files_list[test_files_type]: + the_file = parse_file_entry(entry) + if the_file is not None: + valid_entries.append(the_file) + complex_file_types[test_files_type] = valid_entries + +sicd_files = complex_file_types.get('SICD', []) + +def get_sicd_meta(): + input_file = sicd_files[0] + reader = open_complex(input_file) + return_sicd_meta = reader.sicd_meta + return return_sicd_meta + +def test_sicd_writer_init_failure_no_input(tmp_path): + with pytest.raises(TypeError, + match="missing 1 required positional argument: " + \ + "'file_object'"): + sicd_writer = SICDWriter() + +def test_sicd_writer_init_failure_file_only(tmp_path): + output_file = tmp_path / "out.sicd" + with pytest.raises(ValueError, + match="One of sicd_meta or sicd_writing_details must " + \ + "be provided."): + sicd_writer = SICDWriter(output_file) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_sicd_meta_only(tmp_path): + sicd_meta = get_sicd_meta() + with pytest.raises(TypeError, + match="missing 1 required positional argument: " + \ + "'file_object'"): + sicd_writer = SICDWriter(sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_sicd_writing_details_only(tmp_path): + sicd_meta = get_sicd_meta() + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + with pytest.raises(TypeError, + match="missing 1 required positional argument: " + \ + "'file_object'"): + sicd_writer = SICDWriter(sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_meta(tmp_path): + sicd_meta = get_sicd_meta() + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_sicd_writing_details(tmp_path): + sicd_meta = get_sicd_meta() + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, \ + sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_sicd_meta_invalid_output(tmp_path): + sicd_meta = get_sicd_meta() + output_file = str("/does_not_exist/out.sicd") + with pytest.raises(FileNotFoundError, + match="No such file or directory: " + \ + "'/does_not_exist/out.sicd'"): + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_sicd_writing_details_invalid_output(tmp_path): + sicd_meta = get_sicd_meta() + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str("/does_not_exist/out.sicd") + with pytest.raises(FileNotFoundError, + match="No such file or directory: " + \ + "'/does_not_exist/out.sicd'"): + sicd_writer = SICDWriter(output_file, \ + sicd_writing_details=sicd_writing_details) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_bad_sicd_meta(tmp_path): + sicd_meta = get_sicd_meta() + sicd_meta_bad_string = str(sicd_meta) + "})" + sicd_meta_bad_type = SICDType(sicd_meta_bad_string) + output_file = str(tmp_path / "out.sicd") + with pytest.raises(ValueError, + match="The sicd_meta has un-populated ImageData, and " + \ + "nothing useful can be inferred."): + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta_bad_type) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_writer_init_failure_bad_sicd_writing_details(tmp_path): + sicd_writing_details_bad = "Bad Data" + output_file = str(tmp_path / "out.sicd") + with pytest.raises(TypeError, + match="nitf_writing_details must be of type " + \ + ""): + sicd_writer = SICDWriter(output_file, \ + sicd_writing_details=sicd_writing_details_bad) + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_nitf_writing_details(tmp_path): + sicd_meta = get_sicd_meta() + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, \ + sicd_writing_details=sicd_writing_details) + assert sicd_writer.nitf_writing_details == sicd_writing_details + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_nitf_writing_details_setter_failure(tmp_path): + sicd_meta = get_sicd_meta() + sicd_writing_details = SICDWritingDetails(sicd_meta) + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, \ + sicd_writing_details=sicd_writing_details) + with pytest.raises(ValueError, + match="nitf_writing_details is read-only"): + sicd_writer.nitf_writing_details = sicd_writing_details + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_get_format_function(tmp_path): + sicd_meta = get_sicd_meta() + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, + complex_order='IQ', lut=numpy.array([1, 2])) + + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_get_format_function_failure_bad_band_type(tmp_path): + sicd_meta = get_sicd_meta() + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + with pytest.raises(ValueError, + match="Got unsupported SICD band type definition"): + sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, + complex_order='Steve', \ + lut=numpy.array([1, 2])) + + +@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') +def test_sicd_meta(tmp_path): + sicd_meta = get_sicd_meta() + output_file = str(tmp_path / "out.sicd") + sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) + # Must confirm each piece of the SICDType is equal because there isn't an + # == operator for SICDType. + # First confirm that all CollectionInfo properties are equal. + assert sicd_meta.CollectionInfo.Classification == \ + sicd_writer.sicd_meta.CollectionInfo.Classification + assert sicd_meta.CollectionInfo.CollectorName == \ + sicd_writer.sicd_meta.CollectionInfo.CollectorName + assert sicd_meta.CollectionInfo.IlluminatorName == \ + sicd_writer.sicd_meta.CollectionInfo.IlluminatorName + assert sicd_meta.CollectionInfo.CoreName == \ + sicd_writer.sicd_meta.CollectionInfo.CoreName + assert sicd_meta.CollectionInfo.CollectType == \ + sicd_writer.sicd_meta.CollectionInfo.CollectType + assert sicd_meta.CollectionInfo.CountryCodes == \ + sicd_writer.sicd_meta.CollectionInfo.CountryCodes + assert sicd_meta.CollectionInfo.Parameters == \ + sicd_writer.sicd_meta.CollectionInfo.Parameters + assert sicd_meta.CollectionInfo.RadarMode.ModeType == \ + sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeType + assert sicd_meta.CollectionInfo.RadarMode.ModeID == \ + sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeID + # Confirm ImageCreation properties are equal. + assert sicd_meta.ImageCreation.Application == \ + sicd_writer.sicd_meta.ImageCreation.Application + assert sicd_meta.ImageCreation.DateTime == \ + sicd_writer.sicd_meta.ImageCreation.DateTime + assert sicd_meta.ImageCreation.Site == \ + sicd_writer.sicd_meta.ImageCreation.Site + # TODO: These are different because one is based on the profile from the + # file and the other is based on the profile of the version of sarpy + # we are testing. Skip for now. Determine if these should be the same + # assert sicd_meta.ImageCreation.Profile == \ + # sicd_writer.sicd_meta.ImageCreation.Profile + # Confirm ImageData properties are equal. + assert sicd_meta.ImageData.AmpTable == \ + sicd_writer.sicd_meta.ImageData.AmpTable + assert sicd_meta.ImageData.FirstCol == \ + sicd_writer.sicd_meta.ImageData.FirstCol + assert sicd_meta.ImageData.FirstRow == \ + sicd_writer.sicd_meta.ImageData.FirstRow + assert sicd_meta.ImageData.PixelType == \ + sicd_writer.sicd_meta.ImageData.PixelType + assert sicd_meta.ImageData.SCPPixel.Row == \ + sicd_writer.sicd_meta.ImageData.SCPPixel.Row + assert sicd_meta.ImageData.SCPPixel.Col == \ + sicd_writer.sicd_meta.ImageData.SCPPixel.Col + assert sicd_meta.ImageData.FullImage.NumCols == \ + sicd_writer.sicd_meta.ImageData.FullImage.NumCols + assert sicd_meta.ImageData.FullImage.NumRows == \ + sicd_writer.sicd_meta.ImageData.FullImage.NumRows + # Confirm GeoData properties are equal. + assert sicd_meta.GeoData.EarthModel == \ + sicd_writer.sicd_meta.GeoData.EarthModel + assert sicd_meta.GeoData.SCP.ECF.X == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.X + assert sicd_meta.GeoData.SCP.ECF.Y == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.Y + assert sicd_meta.GeoData.SCP.ECF.Z == \ + sicd_writer.sicd_meta.GeoData.SCP.ECF.Z + assert sicd_meta.GeoData.SCP.LLH.Lat == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.Lat + assert sicd_meta.GeoData.SCP.LLH.Lon == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.Lon + assert sicd_meta.GeoData.SCP.LLH.HAE == \ + sicd_writer.sicd_meta.GeoData.SCP.LLH.HAE + # TODO: Confirm ImageCorners match. + assert numpy.allclose(sicd_meta.GeoData.ImageCorners.FRFC, \ + sicd_writer.sicd_meta.GeoData.ImageCorners.FRFC) + assert numpy.allclose(sicd_meta.GeoData.ImageCorners.FRLC, \ + sicd_writer.sicd_meta.GeoData.ImageCorners.FRLC) + assert numpy.allclose(sicd_meta.GeoData.ImageCorners.LRFC, \ + sicd_writer.sicd_meta.GeoData.ImageCorners.LRFC) + assert numpy.allclose(sicd_meta.GeoData.ImageCorners.LRLC, \ + sicd_writer.sicd_meta.GeoData.ImageCorners.LRLC) + # Confirm Grid properties are equal. + assert sicd_meta.Grid.Col.SS == \ + sicd_writer.sicd_meta.Grid.Col.SS + assert sicd_meta.Grid.Col.UVectECF.X == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.X + assert sicd_meta.Grid.Col.UVectECF.Y == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.Y + assert sicd_meta.Grid.Col.UVectECF.Z == \ + sicd_writer.sicd_meta.Grid.Col.UVectECF.Z + assert sicd_meta.Grid.Row.SS == \ + sicd_writer.sicd_meta.Grid.Row.SS + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.X) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.X) + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Y) + assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Z) + assert sicd_meta.Grid.TimeCOAPoly.Coefs == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.Coefs + assert sicd_meta.Grid.TimeCOAPoly.order1 == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.order1 + assert sicd_meta.Grid.TimeCOAPoly.order2 == \ + sicd_writer.sicd_meta.Grid.TimeCOAPoly.order2 + assert sicd_meta.Grid.Type == \ + sicd_writer.sicd_meta.Grid.Type + assert sicd_meta.Grid.ImagePlane == \ + sicd_writer.sicd_meta.Grid.ImagePlane + # Confirm Timeline properties are equal. + assert sicd_meta.Timeline.IPP == \ + sicd_writer.sicd_meta.Timeline.IPP + assert sicd_meta.Timeline.CollectStart == \ + sicd_writer.sicd_meta.Timeline.CollectStart + assert sicd_meta.Timeline.CollectDuration == \ + sicd_writer.sicd_meta.Timeline.CollectDuration + # Confirm Position properties are equal. + assert pytest.approx(sicd_meta.Position.ARPPoly.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.X) + assert pytest.approx(sicd_meta.Position.ARPPoly.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Y) + assert pytest.approx(sicd_meta.Position.ARPPoly.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Z) + assert pytest.approx(sicd_meta.Position.GRPPoly.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.X) + assert pytest.approx(sicd_meta.Position.GRPPoly.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Y) + assert pytest.approx(sicd_meta.Position.GRPPoly.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Z) + # Confirm TxAPCPoly properties are equal. + if isinstance(sicd_meta.Position.TxAPCPoly, XYZPolyType) \ + and isinstance(sicd_meta.Position.TxAPCPoly, XYZPolyType): + assert pytest.approx(sicd_meta.Position.TxAPCPoly.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.X) + assert pytest.approx(sicd_meta.Position.TxAPCPoly.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Y) + assert pytest.approx(sicd_meta.Position.TxAPCPoly.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Z) + # Confirm RcvAPC properties are equal. + if isinstance(sicd_meta.Position.RcvAPC, XYZPolyType) \ + and isinstance(sicd_meta.Position.RcvAPC, XYZPolyType): + assert pytest.approx(sicd_meta.Position.RcvAPC.X) == \ + pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.X) + assert pytest.approx(sicd_meta.Position.RcvAPC.Y) == \ + pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Y) + assert pytest.approx(sicd_meta.Position.RcvAPC.Z) == \ + pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Z) + # Confirm RadarCollection properties are equal. + assert numpy.allclose(sicd_meta.RadarCollection.TxFrequency.get_array(), \ + sicd_writer.sicd_meta.RadarCollection.TxFrequency.get_array()) + assert sicd_meta.RadarCollection.RefFreqIndex == \ + sicd_writer.sicd_meta.RadarCollection.RefFreqIndex + # TODO: Make this comparison work. + # assert numpy.allclose(sicd_meta.RadarCollection.Waveform.get_array(), \ + # sicd_writer.sicd_meta.RadarCollection.Waveform.get_array()) + assert sicd_meta.RadarCollection.TxPolarization == \ + sicd_writer.sicd_meta.RadarCollection.TxPolarization + + # TODO: Make tests for ImageFormation. This type requires an in depth + # comparison + # assert sicd_meta.ImageFormation.TxPolarization == \ + # sicd_writer.sicd_meta.ImageFormation.TxPolarization + # Confirm SCPCOA properties are equal. + assert pytest.approx(sicd_meta.SCPCOA.SCPTime) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SCPTime) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Z) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Z) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.X) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.X) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Y) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Y) + assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Z) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Z) + assert sicd_meta.SCPCOA.SideOfTrack == \ + sicd_writer.sicd_meta.SCPCOA.SideOfTrack + assert pytest.approx(sicd_meta.SCPCOA.SlantRange) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlantRange) + assert pytest.approx(sicd_meta.SCPCOA.GroundRange) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.GroundRange) + assert pytest.approx(sicd_meta.SCPCOA.DopplerConeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.DopplerConeAng) + assert pytest.approx(sicd_meta.SCPCOA.GrazeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.GrazeAng) + assert pytest.approx(sicd_meta.SCPCOA.IncidenceAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.IncidenceAng) + assert pytest.approx(sicd_meta.SCPCOA.TwistAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.TwistAng) + assert pytest.approx(sicd_meta.SCPCOA.SlopeAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlopeAng) + assert pytest.approx(sicd_meta.SCPCOA.AzimAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.AzimAng) + assert pytest.approx(sicd_meta.SCPCOA.LayoverAng) == \ + pytest.approx(sicd_writer.sicd_meta.SCPCOA.LayoverAng) + + # The following sicd_meta features are optional, so skip them in the test for + # now. + # 'Radiometric', 'Antenna', 'ErrorStatistics', 'MatchInfo', 'RgAzComp', + # 'PFA', 'RMA' diff --git a/tests/io/complex/test_sicd.py b/tests/io/complex/test_sicd.py index e9ae203e..d5c873d8 100644 --- a/tests/io/complex/test_sicd.py +++ b/tests/io/complex/test_sicd.py @@ -1,13 +1,10 @@ -import json -import numpy import os -import pytest +import json import tempfile import unittest -from sarpy.io.complex.converter import conversion_utility, open_complex -from sarpy.io.complex.sicd import SICDReader, SICDWriter, SICDWritingDetails -from sarpy.io.complex.sicd_elements.SICD import SICDType +from sarpy.io.complex.converter import conversion_utility +from sarpy.io.complex.sicd import SICDReader from sarpy.io.complex.sicd_schema import get_schema_path, get_default_version_string @@ -38,361 +35,18 @@ the_version = get_default_version_string() the_schema = get_schema_path(the_version) -def test_sicd_writer_init_failure_no_input(tmp_path): - with pytest.raises(TypeError, - match="missing 1 required positional argument: 'file_object'"): - sicd_writer = SICDWriter() - -def test_sicd_writer_init_failure_file_only(tmp_path): - output_file = tmp_path / "out.sicd" - with pytest.raises(ValueError, - match="One of sicd_meta or sicd_writing_details must be provided."): - sicd_writer = SICDWriter(output_file) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_sicd_meta_only(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - with pytest.raises(TypeError, - match="missing 1 required positional argument: 'file_object'"): - sicd_writer = SICDWriter(sicd_meta=sicd_meta) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_sicd_writing_details_only(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_writing_details = SICDWritingDetails(sicd_meta) - output_file = str(tmp_path / "out.sicd") - with pytest.raises(TypeError, - match="missing 1 required positional argument: 'file_object'"): - sicd_writer = SICDWriter(sicd_writing_details=sicd_writing_details) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_meta(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_sicd_writing_details(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_writing_details = SICDWritingDetails(sicd_meta) - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_sicd_meta_invalid_output(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - output_file = str("/does_not_exist/out.sicd") - with pytest.raises(FileNotFoundError, - match="No such file or directory: '/does_not_exist/out.sicd'"): - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_sicd_writing_details_invalid_output(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_writing_details = SICDWritingDetails(sicd_meta) - output_file = str("/does_not_exist/out.sicd") - with pytest.raises(FileNotFoundError, - match="No such file or directory: '/does_not_exist/out.sicd'"): - sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_bad_sicd_meta(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_meta_bad_string = str(sicd_meta) + "})" - sicd_meta_bad_type = SICDType(sicd_meta_bad_string) - output_file = str(tmp_path / "out.sicd") - with pytest.raises(ValueError, - match="The sicd_meta has un-populated ImageData, and nothing useful can be inferred."): - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta_bad_type) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_writer_init_failure_bad_sicd_writing_details(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - # sicd_meta_bad_string = str(sicd_meta) + "})" - # sicd_meta_bad_type = SICDType(sicd_meta_bad_string) - sicd_writing_details_bad = "Bad Data" - output_file = str(tmp_path / "out.sicd") - with pytest.raises(TypeError, - match="nitf_writing_details must be of type "): - sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details_bad) - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_nitf_writing_details(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_writing_details = SICDWritingDetails(sicd_meta) - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) - assert sicd_writer.nitf_writing_details == sicd_writing_details - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_nitf_writing_details_setter_failure(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - sicd_writing_details = SICDWritingDetails(sicd_meta) - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_writing_details=sicd_writing_details) - with pytest.raises(ValueError, - match="nitf_writing_details is read-only"): - sicd_writer.nitf_writing_details = sicd_writing_details - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_get_format_function(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) - sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, - complex_order='IQ', lut=numpy.array([1, 2])) - - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_get_format_function_failure_bad_band_type(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) - with pytest.raises(ValueError, - match="Got unsupported SICD band type definition"): - sicd_writer.get_format_function(raw_dtype="float", band_dimension=8, - complex_order='Steve', lut=numpy.array([1, 2])) - - -@unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') -def test_sicd_meta(tmp_path): - input_file = sicd_files[0] - reader = open_complex(input_file) - sicd_meta = reader.sicd_meta - output_file = str(tmp_path / "out.sicd") - sicd_writer = SICDWriter(output_file, sicd_meta=sicd_meta) - # Must confirm each piece of the SICDType is equal because there isn't an - # == operator for SICDType. - # First confirm that all CollectionInfo properties are equal. - assert sicd_meta.CollectionInfo.Classification == \ - sicd_writer.sicd_meta.CollectionInfo.Classification - assert sicd_meta.CollectionInfo.CollectorName == \ - sicd_writer.sicd_meta.CollectionInfo.CollectorName - assert sicd_meta.CollectionInfo.IlluminatorName == \ - sicd_writer.sicd_meta.CollectionInfo.IlluminatorName - assert sicd_meta.CollectionInfo.CoreName == \ - sicd_writer.sicd_meta.CollectionInfo.CoreName - assert sicd_meta.CollectionInfo.CollectType == \ - sicd_writer.sicd_meta.CollectionInfo.CollectType - assert sicd_meta.CollectionInfo.CountryCodes == \ - sicd_writer.sicd_meta.CollectionInfo.CountryCodes - assert sicd_meta.CollectionInfo.Parameters == \ - sicd_writer.sicd_meta.CollectionInfo.Parameters - assert sicd_meta.CollectionInfo.RadarMode.ModeType == \ - sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeType - assert sicd_meta.CollectionInfo.RadarMode.ModeID == \ - sicd_writer.sicd_meta.CollectionInfo.RadarMode.ModeID - assert sicd_meta.ImageCreation.Application == \ - sicd_writer.sicd_meta.ImageCreation.Application - assert sicd_meta.ImageCreation.DateTime == \ - sicd_writer.sicd_meta.ImageCreation.DateTime - assert sicd_meta.ImageCreation.Site == \ - sicd_writer.sicd_meta.ImageCreation.Site - # TODO: These are different because one is based on the profile from the - # file and the other is based on the profile of the version of sarpy - # we are testing. Skip for now. Determine if these should be the same - # assert sicd_meta.ImageCreation.Profile == \ - # sicd_writer.sicd_meta.ImageCreation.Profile - assert sicd_meta.ImageData.AmpTable == \ - sicd_writer.sicd_meta.ImageData.AmpTable - assert sicd_meta.ImageData.FirstCol == \ - sicd_writer.sicd_meta.ImageData.FirstCol - assert sicd_meta.ImageData.FirstRow == \ - sicd_writer.sicd_meta.ImageData.FirstRow - assert sicd_meta.ImageData.PixelType == \ - sicd_writer.sicd_meta.ImageData.PixelType - assert sicd_meta.ImageData.SCPPixel.Row == \ - sicd_writer.sicd_meta.ImageData.SCPPixel.Row - assert sicd_meta.ImageData.SCPPixel.Col == \ - sicd_writer.sicd_meta.ImageData.SCPPixel.Col - assert sicd_meta.ImageData.FullImage.NumCols == \ - sicd_writer.sicd_meta.ImageData.FullImage.NumCols - assert sicd_meta.ImageData.FullImage.NumRows == \ - sicd_writer.sicd_meta.ImageData.FullImage.NumRows - assert sicd_meta.GeoData.EarthModel == \ - sicd_writer.sicd_meta.GeoData.EarthModel - assert sicd_meta.GeoData.SCP.ECF.X == \ - sicd_writer.sicd_meta.GeoData.SCP.ECF.X - assert sicd_meta.GeoData.SCP.ECF.Y == \ - sicd_writer.sicd_meta.GeoData.SCP.ECF.Y - assert sicd_meta.GeoData.SCP.ECF.Z == \ - sicd_writer.sicd_meta.GeoData.SCP.ECF.Z - assert sicd_meta.GeoData.SCP.LLH.Lat == \ - sicd_writer.sicd_meta.GeoData.SCP.LLH.Lat - assert sicd_meta.GeoData.SCP.LLH.Lon == \ - sicd_writer.sicd_meta.GeoData.SCP.LLH.Lon - assert sicd_meta.GeoData.SCP.LLH.HAE == \ - sicd_writer.sicd_meta.GeoData.SCP.LLH.HAE - # TODO: Confirm ImageCorners match. - # assert sicd_meta.GeoData.ImageCorners.FRFC == \ - # sicd_writer.sicd_meta.GeoData.ImageCorners.FRFC - # assert sicd_meta.GeoData.ImageCorners.FRLC == \ - # sicd_writer.sicd_meta.GeoData.ImageCorners.FRLC - # assert sicd_meta.GeoData.ImageCorners.LRFC == \ - # sicd_writer.sicd_meta.GeoData.ImageCorners.LRFC - # assert sicd_meta.GeoData.ImageCorners.LRLC == \ - # sicd_writer.sicd_meta.GeoData.ImageCorners.LRLC - assert sicd_meta.Grid.Col.SS == \ - sicd_writer.sicd_meta.Grid.Col.SS - assert sicd_meta.Grid.Col.UVectECF.X == \ - sicd_writer.sicd_meta.Grid.Col.UVectECF.X - assert sicd_meta.Grid.Col.UVectECF.Y == \ - sicd_writer.sicd_meta.Grid.Col.UVectECF.Y - assert sicd_meta.Grid.Col.UVectECF.Z == \ - sicd_writer.sicd_meta.Grid.Col.UVectECF.Z - assert sicd_meta.Grid.Row.SS == \ - sicd_writer.sicd_meta.Grid.Row.SS - assert pytest.approx(sicd_meta.Grid.Row.UVectECF.X) == \ - pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.X) - assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Y) == \ - pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Y) - assert pytest.approx(sicd_meta.Grid.Row.UVectECF.Z) == \ - pytest.approx(sicd_writer.sicd_meta.Grid.Row.UVectECF.Z) - assert sicd_meta.Grid.TimeCOAPoly.Coefs == \ - sicd_writer.sicd_meta.Grid.TimeCOAPoly.Coefs - assert sicd_meta.Grid.TimeCOAPoly.order1 == \ - sicd_writer.sicd_meta.Grid.TimeCOAPoly.order1 - assert sicd_meta.Grid.TimeCOAPoly.order2 == \ - sicd_writer.sicd_meta.Grid.TimeCOAPoly.order2 - assert sicd_meta.Grid.Type == \ - sicd_writer.sicd_meta.Grid.Type - assert sicd_meta.Grid.ImagePlane == \ - sicd_writer.sicd_meta.Grid.ImagePlane - assert sicd_meta.Timeline.IPP == \ - sicd_writer.sicd_meta.Timeline.IPP - assert sicd_meta.Timeline.CollectStart == \ - sicd_writer.sicd_meta.Timeline.CollectStart - assert sicd_meta.Timeline.CollectDuration == \ - sicd_writer.sicd_meta.Timeline.CollectDuration - assert pytest.approx(sicd_meta.Position.ARPPoly.X) == \ - pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.X) - assert pytest.approx(sicd_meta.Position.ARPPoly.Y) == \ - pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Y) - assert pytest.approx(sicd_meta.Position.ARPPoly.Z) == \ - pytest.approx(sicd_writer.sicd_meta.Position.ARPPoly.Z) - assert pytest.approx(sicd_meta.Position.GRPPoly.X) == \ - pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.X) - assert pytest.approx(sicd_meta.Position.GRPPoly.Y) == \ - pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Y) - assert pytest.approx(sicd_meta.Position.GRPPoly.Z) == \ - pytest.approx(sicd_writer.sicd_meta.Position.GRPPoly.Z) - ## TxAPCPoly is empty in test data. - # TODO: Make a __eq__ operator that handles NoneType - # assert pytest.approx(sicd_meta.Position.TxAPCPoly.X) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.X) - # assert pytest.approx(sicd_meta.Position.TxAPCPoly.Y) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Y) - # assert pytest.approx(sicd_meta.Position.TxAPCPoly.Z) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.TxAPCPoly.Z) - ## TxAPCPoly is empty in test data. - # TODO: Make a __eq__ operator that handles NoneType - # assert pytest.approx(sicd_meta.Position.RcvAPC.X) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.X) - # assert pytest.approx(sicd_meta.Position.RcvAPC.Y) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Y) - # assert pytest.approx(sicd_meta.Position.RcvAPC.Z) == \ - # pytest.approx(sicd_writer.sicd_meta.Position.RcvAPC.Z) - assert numpy.allclose(sicd_meta.RadarCollection.TxFrequency.get_array(), \ - sicd_writer.sicd_meta.RadarCollection.TxFrequency.get_array()) - assert sicd_meta.RadarCollection.RefFreqIndex == \ - sicd_writer.sicd_meta.RadarCollection.RefFreqIndex - # TODO: Make this comparison work. - # assert numpy.allclose(sicd_meta.RadarCollection.Waveform.get_array(), \ - # sicd_writer.sicd_meta.RadarCollection.Waveform.get_array()) - assert sicd_meta.RadarCollection.TxPolarization == \ - sicd_writer.sicd_meta.RadarCollection.TxPolarization - - # TODO: Make tests for ImageFormation. This type requires an in depth - # comparison - # assert sicd_meta.ImageFormation.TxPolarization == \ - # sicd_writer.sicd_meta.ImageFormation.TxPolarization - - assert pytest.approx(sicd_meta.SCPCOA.SCPTime) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.SCPTime) - assert pytest.approx(sicd_meta.SCPCOA.ARPPos.X) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.X) - assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Y) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Y) - assert pytest.approx(sicd_meta.SCPCOA.ARPPos.Z) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPPos.Z) - assert pytest.approx(sicd_meta.SCPCOA.ARPVel.X) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.X) - assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Y) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Y) - assert pytest.approx(sicd_meta.SCPCOA.ARPVel.Z) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPVel.Z) - assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.X) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.X) - assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Y) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Y) - assert pytest.approx(sicd_meta.SCPCOA.ARPAcc.Z) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.ARPAcc.Z) - assert sicd_meta.SCPCOA.SideOfTrack == \ - sicd_writer.sicd_meta.SCPCOA.SideOfTrack - assert pytest.approx(sicd_meta.SCPCOA.SlantRange) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlantRange) - assert pytest.approx(sicd_meta.SCPCOA.GroundRange) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.GroundRange) - assert pytest.approx(sicd_meta.SCPCOA.DopplerConeAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.DopplerConeAng) - assert pytest.approx(sicd_meta.SCPCOA.GrazeAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.GrazeAng) - assert pytest.approx(sicd_meta.SCPCOA.IncidenceAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.IncidenceAng) - assert pytest.approx(sicd_meta.SCPCOA.TwistAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.TwistAng) - assert pytest.approx(sicd_meta.SCPCOA.SlopeAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.SlopeAng) - assert pytest.approx(sicd_meta.SCPCOA.AzimAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.AzimAng) - assert pytest.approx(sicd_meta.SCPCOA.LayoverAng) == \ - pytest.approx(sicd_writer.sicd_meta.SCPCOA.LayoverAng) - - # The following sicd_meta features are optional, so skip them in the test for - # now. - # 'Radiometric', 'Antenna', 'ErrorStatistics', 'MatchInfo', 'RgAzComp', - # 'PFA', 'RMA' class TestSICDWriting(unittest.TestCase): - def setUp(self): - self.sicd_files = complex_file_types.get('SICD', []) - if len(self.sicd_files) == 0 : - self.skipTest('No sicd files found') - + @unittest.skipIf(len(sicd_files) == 0, 'No sicd files found') def test_sicd_creation(self): - for fil in self.sicd_files: + for fil in sicd_files: reader = SICDReader(fil) # check that sicd structure serializes according to the schema if etree is not None: sicd = reader.get_sicds_as_tuple()[0] xml_doc = etree.fromstring(sicd.to_xml_bytes()) - print(xml_doc) xml_schema = etree.XMLSchema(file=the_schema) with self.subTest(msg='validate xml produced from sicd structure'): self.assertTrue(xml_schema.validate(xml_doc), @@ -408,4 +62,4 @@ def test_sicd_creation(self): with self.subTest(msg='Test writing a single row of the sicd file {}'.format(fil)): with tempfile.TemporaryDirectory() as tmpdirname: - conversion_utility(reader, tmpdirname, row_limits=(0, 1)) + conversion_utility(reader, tmpdirname, row_limits=(0, 1)) \ No newline at end of file From 8dcfeced414cae979dae79f0e4e1e2031fc7a136 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 10 Jul 2025 18:06:00 +0000 Subject: [PATCH 26/30] Update test_remap.py correct test to match new signature --- tests/visualization/test_remap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/visualization/test_remap.py b/tests/visualization/test_remap.py index 7961590c..71975b53 100644 --- a/tests/visualization/test_remap.py +++ b/tests/visualization/test_remap.py @@ -329,7 +329,7 @@ def test_remap_names(self): def test_get_registered_remap(self): with self.assertRaises(KeyError): remap.get_registered_remap("__fake__") - self.assertEqual(remap.get_registered_remap("__fake__", 8, "default"), "default") + self.assertEqual(remap.get_registered_remap("__fake__", "default", 8 ), "default") def test_get_remap_list(self): remap_list = remap.get_remap_list() From 6d6cff9afa9d404bf5896c9bd8d99113c84c6cf5 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 10 Jul 2025 16:28:14 -0400 Subject: [PATCH 27/30] Update remap.py style changes --- sarpy/visualization/remap.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index 54b36b8d..dc769fc4 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -1831,14 +1831,13 @@ def register_remap( if not isinstance(remap_function, RemapFunction): raise TypeError('remap_function must be an instance of RemapFunction.') - remap_name = remap_function.name + if remap_function.bit_depth == 16: - rm_name = remap_name + '_' + str( remap_function.bit_depth ) + remap_name = remap_function.name + '_' + str( remap_function.bit_depth ) else: - rm_name = remap_name - remap_name = rm_name - + remap_name = remap_function.name + if remap_name not in _REMAP_DICT: _REMAP_DICT[remap_name] = remap_function elif overwrite: From 5911f6ad5bb84276fea67f14a154ed6bdca15c62 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 10 Jul 2025 18:58:53 -0400 Subject: [PATCH 28/30] Update test_remap.py add some tests around 16 bit remapping --- tests/visualization/test_remap.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/visualization/test_remap.py b/tests/visualization/test_remap.py index 71975b53..b369a827 100644 --- a/tests/visualization/test_remap.py +++ b/tests/visualization/test_remap.py @@ -331,6 +331,29 @@ def test_get_registered_remap(self): remap.get_registered_remap("__fake__") self.assertEqual(remap.get_registered_remap("__fake__", "default", 8 ), "default") + def test_get_registered_remap_required_param_only(self): + self.assertEqual(remap.get_registered_remap("linear" ).name, "linear") + + def test_get_registered_remap_required_param_default(self): + self.assertEqual(remap.get_registered_remap("linear", "default" ).name, "linear") + + def test_get_registered_remap_required_param_default_bit_depth(self): + self.assertEqual(remap.get_registered_remap("linear" ).name, "linear") + self.assertEqual(remap.get_registered_remap("linear" ).bit_depth, 8) + + + def test_get_registered_remap_bitdepth_param(self): + self.assertEqual(remap.get_registered_remap("linear", bit_depth= 16 ).name, "linear") + self.assertEqual(remap.get_registered_remap("linear", bit_depth= 16 ).bit_depth, 16) + + def test_get_registered_remap_falure_bitdepth_param(self): + with self.assertRaises(KeyError): + remap.get_registered_remap("linear", bit_depth= 32 ) + + def test_get_registered_remap_falure_not_registered(self): + with self.assertRaises(KeyError): + remap.get_registered_remap("steve" ) + def test_get_remap_list(self): remap_list = remap.get_remap_list() self.assertSetEqual(set(item[0] for item in remap_list), set(remap.get_remap_names())) From 8860dc9a7c677537bce4baeb18b750b83b4aa359 Mon Sep 17 00:00:00 2001 From: jonlorax Date: Thu, 10 Jul 2025 19:18:37 -0400 Subject: [PATCH 29/30] Update remap.py test to make sure remap bit depth in allowed values 8 or 16 --- sarpy/visualization/remap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sarpy/visualization/remap.py b/sarpy/visualization/remap.py index dc769fc4..2fa925bf 100644 --- a/sarpy/visualization/remap.py +++ b/sarpy/visualization/remap.py @@ -1952,6 +1952,9 @@ def get_registered_remap( if not _DEFAULTS_REGISTERED: _register_defaults() + if int( bit_depth ) not in [ 8, 16 ]: + raise KeyError('Unregistered remap name `{}` with bit_depth `{}`'.format( remap_name, bit_depth )) + if int( bit_depth ) == 16: rm_name = remap_name + '_' + str( bit_depth ) else: From 9fa108d90506cb26f169ab6377f03c9bcd8bccb9 Mon Sep 17 00:00:00 2001 From: petersontex Date: Fri, 18 Jul 2025 13:18:05 -0400 Subject: [PATCH 30/30] Update build.yml Add GitHub Action to build releases and release candidates and push the artifacts to pypi. --- .github/workflows/build.yml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8a8522e2..2531548f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: Build packages -on: +on: push: branches: - master @@ -39,7 +39,8 @@ jobs: if: contains(github.ref, 'refs/heads/integration/') run: | echo ${{ steps.extract_version.outputs.raw_version }} - rc_version=${{ steps.extract_version.outputs.raw_version }}rc0 + rc_version=${{ steps.extract_version.outputs.raw_version }}rc1 + sed -i -e "s/__version__ = \"${{ steps.extract_version.outputs.raw_version }}/__version__ = \"$rc_version/g" sarpy/__about__.py echo "version=$rc_version" >> $GITHUB_OUTPUT id: extract_release_candidate_version - name: Install pypa/build @@ -94,3 +95,24 @@ jobs: prerelease: false target_commitish: ${{github.ref}} files: dist/* + + publish-to-pypi: + name: Publish to PyPI + needs: release + runs-on: ubuntu-latest + environment: + name: development + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build.outputs.version }} + path: dist + merge-multiple: true + - run: ls -R dist + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://pypi.org/legacy/