def_get_ome_dict_tifffile(img_path):# WARNING: works only with recent versions of tifffile not 0.15.1ifnottifffile.TiffFile(img_path).is_ome:raiseNotAnOmeFile(f"File {img_path} is not a valid ome.tif file")ome_metadata=tifffile.tiffcomment(img_path)ome_dict=tifffile.xml2dict(ome_metadata)returnome_dictdef_get_ome_dict_pil(img_path):not_an_ome_msg=f"File {img_path} is not a valid ome.tif file"ifnotimg_path.endswith('.ome.tif'):# Weak but can't rely on tifffileraiseNotAnOmeFile(not_an_ome_msg)img=Image.open(img_path)if270notinimg.tag.keys():raiseNotAnOmeFile(not_an_ome_msg)ome_metadata=img.tag[270][0]importxmltodictome_dict=xmltodict.parse(ome_metadata)returnome_dict
[docs]defparse_overlaps(img_path):parser,ome_dict=get_ome_dict(img_path)# custom_props = ome_dict['OME']['CustomAttributes']['Properties']['prop']ifparser=='tifffile':custom_props=ome_dict['OME']['CustomAttributes']['PropArray']x_overlap=round(custom_props['xyz-Table_X_Overlap']['Value'])y_overlap=round(custom_props['xyz-Table_Y_Overlap']['Value'])elifparser=='PIL':try:custom_props=ome_dict['OME']['ca:CustomAttributes']['PropArray']x_overlap=round(float(custom_props['xyz-Table_X_Overlap']['@Value']))y_overlap=round(float(custom_props['xyz-Table_Y_Overlap']['@Value']))exceptKeyError:custom_props=ome_dict['OME']['ca:CustomAttributes']['Properties']['prop']forattrincustom_props:if'@label'inattr.keys():ifattr['@label']=='xyz-Table X Overlap':x_overlap=round(attr['@Value'])elifattr['@label']=='xyz-Table X Overlap':y_overlap=round(attr['@Value'])else:raiseValueError(f'parser type "{parser}" is not recognised')# x_overlap = [elt['Value'] for elt in custom_props if elt['label'] == 'xyz-Table X Overlap'][0]# y_overlap = [elt['Value'] for elt in custom_props if elt['label'] == 'xyz-Table Y Overlap'][0]returnx_overlap,y_overlap
[docs]defdefine_auto_stitching_params(img_path,stitching_cfg):overlaps=[stitching_cfg['rigid']['overlap_x'],stitching_cfg['rigid']['overlap_y']]ifany([overlap=='auto'foroverlapinoverlaps]):parsed_overlaps=parse_overlaps(img_path)projection_thickness=stitching_cfg['rigid']['project_thickness']foriinrange(len(overlaps)):# WARNING: skips Z below but no useifoverlaps[i]=='auto':overlaps[i]=parsed_overlaps[i]ifprojection_thickness[i]=='auto':projection_thickness[i]=overlaps[i]# TODO: see if 0.9*overlaps[i] insteadreturnoverlaps,projection_thickness
[docs]defdefine_auto_resolution(img_path,cfg_res):ifcfg_res=='auto':cfg_res=('auto',)*3out_res=deepcopy(cfg_res)ifnotcfg_res.count('auto'):returnout_resparsed_res=Nonetry:parsed_res=parse_img_res(img_path)exceptNotAnOmeFilease:print(str(e))print('Defaulting to config values')exceptKeyErrorase:print(f"Could not find resolution for image {img_path}, defaulting to config")ifparsed_resisNoneandcfg_res.count('auto'):raiseMetadataError(f"Could not determine auto config for file {img_path}")fori,ax_resinenumerate(cfg_res):ifax_res=='auto':out_res[i]=parsed_res[i]returnout_res
[docs]classPatternFinder:# TODO: from_df class_methoddef__init__(self,folder,tiff_list=None,df=None,axes_order=None):self.folder=folderiftiff_listisnotNone:self.df=self.get_df_from_file_list(tiff_list)elifdfisnotNone:self.df=dfelse:raiseValueError('Must supply at least tiff_list or df')self.pattern=Pattern(self.pattern_from_df(self.df))ifaxes_orderisnotNone:self.pattern.axes_order=axes_order@propertydefx_values(self):returnself.df.loc[:,self.pattern.x_rng].drop_duplicates().sort_values(self.pattern.x_rng).values@propertydefy_values(self):returnself.df.loc[:,self.pattern.y_rng].drop_duplicates().sort_values(self.pattern.y_rng).values@propertydefz_values(self):returnself.df.loc[:,self.pattern.z_rng].drop_duplicates().sort_values(self.pattern.z_rng).values@propertydefc_values(self):returnself.df.loc[:,self.pattern.c_rng].drop_duplicates().sort_values(self.pattern.c_rng).values@propertydeftiff_list(self):returnself.get_tiff_list(self.df)@propertydeftiff_paths(self):return[os.path.join(self.folder,f_name)forf_nameinself.tiff_list]
[docs]@classmethoddeffrom_mixed_tiff_lists(cls,folder,tiff_list,axes_order=None):df=cls.get_df_from_file_list(tiff_list)pattern=Pattern(cls.pattern_from_df(df))finders=cls.split_channel(folder,df,pattern,axes_order=axes_order)iffindersisnotNone:returnfinderselse:print(f'Could not find different channels in Pattern {pattern}')returncls(folder,tiff_list,axes_order=axes_order)
[docs]@classmethoddefsplit_axis(cls,folder,df,pattern,axis_letter,axes_order=None):c_idx=[ifori,chunkinenumerate(pattern.chunks)ifchunk.endswith(axis_letter)]ifc_idx:cluster_idx=c_idx[0]else:returnifaxes_orderisnotNone:# FIXME: not supplied by UIforkinaxes_order.keys():# Remove C from axes_order if we split by Cifaxes_order[k]>axes_order['c']:axes_order[k]-=1axes_order['c']=Nonecolumns=pattern.digit_clusters[cluster_idx]axis_values=df[columns].drop_duplicates().valuespattern_finders=[]foraxis_valinaxis_values:sub_df=df.copy()forcol,vinzip(columns,axis_val):sub_df=sub_df[sub_df[col]==v]tiff_list=cls.get_tiff_list(sub_df)pattern_finders.append(cls(folder,tiff_list,axes_order=axes_order))returnpattern_finders
@staticmethoddef__fix_pattern(pattern):""" When not all digits are used in a zero padded pattern and were not detected. Parameters ---------- pattern Returns ------- """pattern=list(pattern)fori,cinenumerate(pattern[::-1]):# print(i, c)ifc=='?':ifpattern[::-1][i+1]=='0':pattern[(len(pattern)-1)-(i+1)]='?'return''.join(pattern)
[docs]classPattern:def__init__(self,pattern_str):self.chunks=[]self.digit_clusters=[]self.pattern_elements=[]# e.g. ['<X,2>', '<Y,2>']self.pattern_str=pattern_strself.parse_pattern(pattern_str)self.axes_order={'x':None,'y':None,'z':None,'c':None}def__str__(self):returnself.pattern_str@propertydefclearmap_pattern(self):out=''foriinrange(len(self.chunks)):out+=self.chunks[i]ifi<len(self.digit_clusters):ax=[kfork,vinself.axes_order.items()ifv==i][0]out+=f'<{ax.upper()},{len(self.digit_clusters[i])}>'returnout@propertydefx_order(self):returnself.axes_order['x']@propertydefy_order(self):returnself.axes_order['y']@propertydefz_order(self):returnself.axes_order['z']@propertydefc_order(self):returnself.axes_order['c']@propertydefx_rng(self):ifself.x_orderisNone:returnelse:returnself.digit_clusters[self.x_order]@propertydefy_rng(self):ifself.y_orderisNone:returnelse:returnself.digit_clusters[self.y_order]@propertydefz_rng(self):ifself.z_orderisNone:returnelse:returnself.digit_clusters[self.z_order]@propertydefc_rng(self):ifself.c_orderisNone:returnelse:returnself.digit_clusters[self.c_order]
[docs]defstack_tiles_to_columns(sample_folder,axes_order,remove_tiles=False):""" Parameters ---------- sample_folder str: The folder where the data is located axes_order dict: A dictionary of the type {'x': 1, 'y': 0, 'z': 3, 'c': 2} indicating the order of each axis remove_tiles bool: Whether to remove the individual tiles at the end Returns ------- """pattern_finders=pattern_finders_from_base_dir(sample_folder,axes_order=axes_order)images_to_remove=[]forpat_finderintqdm(pattern_finders):foryintqdm(pat_finder.y_values):forxintqdm(pat_finder.x_values,leave=False):img_paths=pat_finder.get_sub_tiff_paths(x=x,y=y)sub_pat=PatternFinder(folder=sample_folder,tiff_list=img_paths,axes_order=pat_finder.pattern.axes_order)ws=Workspace('CellMap',directory=sub_pat.folder)ws.update({'raw':sub_pat.pattern.clearmap_pattern.replace(sub_pat.folder,'')})# ws.info()new_path=pat_finder.sub_pattern_str(x=x,y=y)new_path=new_path.replace('_xyz-Table Z????','')new_path=os.path.join(pat_finder.folder,new_path)clearmap_io.convert(ws.source('raw'),new_path)images_to_remove.extend(img_paths)withopen('/tmp/file_to_rm.txt','a')ashandle:handle.write('\n'.join(images_to_remove))ifremove_tiles:forf_pathinimages_to_remove:os.remove(f_path)