"""Module for processing SVS metadata and generating fsspec zarr JSON file.The fsspec zarr json file is meant to be used in case SVS or TIFF filescan be accessed using byte range HTTP API.The fsspec zarr json file can be opened using FsspecJsonWSIReader."""from__future__importannotationsimportjsonimportsysfromdatetimeimportdatetimefrompathlibimportPathfromtypingimportAnyfromtifffileimportTiffFile,tiff2fsspecfromtiatoolbox.wsicore.wsireaderimportTIFFWSIReaderDelegate# ConstantsEXPECTED_KEY_VALUE_PAIRS=2EXPECTED_ARG_COUNT=4URL_PLACEHOLDER="https://replace.me/"
[docs]defconvert_metadata(metadata:dict)->dict:"""Convert metadata to JSON-compatible format."""ifisinstance(metadata,dict):return{key:convert_metadata(value)forkey,valueinmetadata.items()}ifisinstance(metadata,list):return[convert_metadata(item)foriteminmetadata]ifisinstance(metadata,datetime):returnmetadata.isoformat()# Convert datetime to ISO 8601 stringreturnmetadata
[docs]defreplace_url(data:dict[str,Any],output_path:Path,old_url:str,new_url:str)->None:"""Replace URL in the JSON file."""forvalueindata.values():ifisinstance(value,list)andvalue[0]==old_url:value[0]=new_urlwithoutput_path.open("w")asjson_file:json.dump(data,json_file,indent=2)
[docs]defmain(svs_file_path:str,json_file_path:str,final_url:str)->None:"""Main function to process an SVS file. Args: svs_file_path (str): The local file path of the SVS file to be processed. json_file_path (str): The file path where the output JSON will be saved. final_url (str): The URL where the SVS file is stored online and can be accessed via HTTP byte range API. Example: main('/path/to/CMU-1-Small-Region.svs', '/path/to/CMU-1-Small-Region.json', 'https://tiatoolbox.dcs.warwick.ac.uk/sample_wsis/CMU-1-Small-Region.svs') """url_to_replace=f"{URL_PLACEHOLDER}{Path(svs_file_path).name}"tiff=TiffFile(svs_file_path)tiff_file_pages=tiff.pages# Generate fsspec JSONtiff2fsspec(svs_file_path,url=URL_PLACEHOLDER,out=json_file_path)iftiff.is_svs:metadata=TIFFWSIReaderDelegate.parse_svs_metadata(tiff_file_pages)else:# pragma: no covermetadata=TIFFWSIReaderDelegate.parse_generic_tiff_metadata(tiff_file_pages)# Convert metadata to JSON-compatible formatmetadata_serializable=convert_metadata(metadata)# Read the JSON data from the filejson_path=Path(json_file_path)withjson_path.open()asfile:json_data=json.load(file)# Decode `.zattrs` JSON string into a dictionaryzattrs=json.loads(json_data[".zattrs"])# Ensure "multiscales" exists and is a listif"multiscales"notinzattrsornotisinstance(zattrs["multiscales"],list):# pragma: no coverzattrs["multiscales"]=[{}]# Initialize as a list with an empty dictionary# Update metadata into `.zattrs`zattrs["multiscales"][0]["metadata"]=metadata_serializable# Convert back to a JSON stringjson_data[".zattrs"]=json.dumps(zattrs)# Replace URLs in the JSON filereplace_url(json_data,json_path,url_to_replace,final_url)