Skip to content

Commit

Permalink
refactoring continues
Browse files Browse the repository at this point in the history
  • Loading branch information
Victor Perez Meza committed Sep 23, 2024
1 parent 130da6b commit b38c168
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 152 deletions.
171 changes: 160 additions & 11 deletions macsima2mc/helpers.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,62 @@
import templates
from templates import info_dic
import re
import pandas as pd
import tifffile as tifff
from bs4 import BeautifulSoup
import numpy as np
from pathlib import Path



def extract_values(pattern, strings, number_cast=True):
def merge_dicts(list_of_dicts):
merged_dict = {}
for d in list_of_dicts:
for key, value in d.items():
if key in merged_dict:
merged_dict[key].append(value)
else:
merged_dict[key] = [value]
return merged_dict

def extract_values(target_pattern, strings,number_cast=True):
return [
(int(m.group(1)) if number_cast else m.group(1))
if (m := re.search(pattern, s))
if (m := re.search(target_pattern, s))
else None
for s in strings
]


def xy_coords(tile_abs_path):

with tifff.TiffFile(tile_abs_path) as tif:
metadata = tif.ome_metadata

ome = BeautifulSoup(metadata, "xml")

return {
"position_x": float(ome.StageLabel["X"]),
"position_y": float(ome.StageLabel["Y"]),
"position_x_unit": ome.StageLabel["XUnit"],
"position_y_unit": ome.StageLabel["YUnit"],
"physical_size_x": float(ome.Pixels["PhysicalSizeX"]),
"physical_size_x_unit": ome.Pixels["PhysicalSizeXUnit"],
"physical_size_y": float(ome.Pixels["PhysicalSizeY"]),
"physical_size_y_unit": ome.Pixels["PhysicalSizeXUnit"],
"size_x":ome.Pixels["SizeX"],
"size_y":ome.Pixels["SizeY"],
"type": ome.Pixels["Type"],#bit_depth
"significant_bits": int(ome.Pixels["SignificantBits"]),
}



def extract_img_info(full_img_path, platform_pattern,ref_marker= 'DAPI',dir_version=1):
def cycle_info(cycle_path, platform_pattern,ref_marker= 'DAPI'):
'''
This function reads the images produced by the MACSima device and returns the acquistion information
specified in the image name.
inputs:
-img_path[Path]= full path to the image
-cycle_path[Path]= full path to the cycle folder
-ref[str]=marker of reference used for registration
-source[str]= valid values 'Antigen' or 'Bleach'
-dir_version[int]=version of the macsima folder and file naming structure. Valid values are 1 or 2.
Expand All @@ -30,13 +66,126 @@ def extract_img_info(full_img_path, platform_pattern,ref_marker= 'DAPI',dir_vers
-info[dict]=dictionary with acquisition information, ROI, rack, exposure time etc.
'''

full_image_paths = list(cycle_path.glob("*.tif"))
file_names = [x.name for x in full_image_paths]

info=info_dic(platform_pattern)
info['full_path']=full_img_path
info['img_name']=Path(full_img_path).name
for key,value in platform_pattern.items():
info[key]=

info['full_path']=full_image_paths
info['img_name']=file_names

for feat,value in platform_pattern.items():

info[feat]=extract_values(target_pattern=value, strings=file_names,number_cast=False)

df=pd.DataFrame(info)
df.loc[df['filter']==ref_marker,'marker']=ref_marker

df.insert(loc=df.shape[1], column="exposure_level", value=0)
df["exposure_time"] = df["exposure_time"].astype(float)
df["exposure_level"] = ( df.groupby(["source","marker","filter"])["exposure_time"].rank(method="dense")).astype(int)


return df


def append_extra_info(cycle_info_df):

pos=list( map(xy_coords, cycle_info_df['full_path'].values) )

for key,val in merge_dicts(pos).items():
cycle_info_df.insert(loc=cycle_info_df.shape[1], column=key, value=val)

return cycle_info_df

def conform_markers(mf_tuple,ref_marker='DAPI'):

markers=[tup for tup in mf_tuple if tup[0]!=ref_marker]
markers.insert(0,(ref_marker,ref_marker))
return markers

def any_ref(mf_tuple,ref_marker='DAPI'):
exist_ref=False
for m in mf_tuple:
if m[0]==ref_marker:
exist_ref=True
break
return exist_ref



def init_stack(ref_tile_index,groupby_obj,marker_filter_map):
ref_tile=groupby_obj.get_group(ref_tile_index)
total_tiles=len(groupby_obj)
width=ref_tile.size_x.values[0]
height=ref_tile.size_y.values[0]
depth=total_tiles*len(marker_filter_map)
stack=np.zeros( (depth,int(height),int(width)) ,dtype=ref_tile.type.values[0] )

return stack

def cast_stack_name(cycle_no,acq_group_index,marker_filter_map):
#acq_group_index('source','rack','well','roi','exposure_level')
markers='__'.join([element[0] for element in marker_filter_map ])
filters='__'.join([element[1] for element in marker_filter_map ])
cycle_no=int(cycle_no)

name='cycle-{C}-src-{S}-rack-{R}-well-{W}-roi-{ROI}-exp-{E}-markers-{M}-filters-{F}.{img_format}'.format(
C=f'{cycle_no:03d}',
S=acq_group_index[0],
E=acq_group_index[4],
R=acq_group_index[1],
W=acq_group_index[2],
ROI=acq_group_index[3],
M=markers,
F=filters,
img_format='ome.tiff'
)

return name

def cast_outdir_name(tup):
#tuple('source','rack','well','roi','exposure_level'])
name='rack-{R}-well-{W}-roi-{ROI}-exp-{E}'.format(
R=tup[1],
W=tup[2],
ROI=tup[3],
E=tup[4]
)

return name




return info
def create_stack(cycle_info_df,output_dir,ref_marker='DAPI'):
acq_group=cycle_info_df.groupby(['source','rack','well','roi','exposure_level'])
acq_index=list(acq_group.indices.keys())
output_dir=Path(output_dir)
for index in acq_index:
(output_dir / cast_outdir_name(index) ).mkdir(parents=True, exist_ok=True)
group=acq_group.get_group(index)
#use tile 1 as reference to determine the heigh and width of the tiles
tile_no=group.tile.values
ref_tile=group.groupby(['tile']).get_group(tile_no[0])
marker_filter_map=list(ref_tile.groupby(["marker","filter"]).indices.keys())
exist_ref=any_ref(marker_filter_map,ref_marker)
if not exist_ref:
index_aux=list(index)
index_aux[-1]=1
index_aux=tuple(index_aux)
aux_group=acq_group.get_group(index_aux)
aux_group=aux_group.loc[aux_group['marker']==ref_marker]
group=pd.concat([group,aux_group])

groups_of_tiles=group.groupby(['tile'])
conformed_markers =conform_markers(marker_filter_map,ref_marker)
stack=init_stack(tile_no[0],groups_of_tiles,conformed_markers)
counter=0
for tile_no,frame in groups_of_tiles:
for marker,filter in conformed_markers:
target_path=frame.loc[ (frame['marker']==marker) & (frame['filter']==filter) ].full_path.values[0]
stack[counter,:,:]=tifff.imread(Path(target_path))
counter+=1
stack_name =cast_stack_name(frame.cycle.iloc[0],index,conformed_markers)
tifff.imwrite( output_dir / cast_outdir_name(index) /stack_name,stack,photometric='minisblack' )
16 changes: 9 additions & 7 deletions macsima2mc/macsima2mc.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import helpers
from pathlib import Path
from templates import macsima_pattern,info_dic
from templates import macsima_pattern
import pandas as pd
import os

#data_folder=Path('D:/macsima_data_samples/macsima_data_v1/001_AntigenCycle')
#images=list(data_folder.glob("*.tif"))

img_test=Path('000_BleachCycle_None_V50_PE_16bit_M-20x-S Fluor full sensor_B-1_R-2_W-2_G-1_F-30_E-144.0.tif')

def extract_img_info(img_test, macsima_pattern, info_dic,ref_marker= 'DAPI',dir_version=1)

input_test_folder=Path("D:/macsima_data_samples/macsima_data_v2/6_Cycle1")
output_test_folder=Path('D:/test_folder')

info=helpers.cycle_info(input_test_folder, macsima_pattern(version=2),ref_marker= 'DAPI')
info_extra=helpers.append_extra_info(info)
info_extra.to_csv( output_test_folder / 'cycle_{c}_info.csv'.format(c=f'{6:03d}'), index=False )
helpers.create_stack(info_extra,output_test_folder,ref_marker='DAPI')



Expand Down
117 changes: 117 additions & 0 deletions macsima2mc/ome_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/python
import ome_types
import pandas as pd
import tifffile as tifff
from bs4 import BeautifulSoup
from uuid import uuid4
import copy
from ome_types import from_tiff,to_xml
from ome_types.model import OME,Image,Instrument,Pixels,TiffData,Channel,Plane
import ome_types.model
import platform

def TIFF_block(no_of_channels):

TIFF=[
TiffData(
first_c=ch,
ifd='',
plane_count=1
)
for ch in range(0,no_of_channels)
]

return TIFF

def PLANE_block(no_of_channels):

PLANE=[
Plane(
the_c=ch,
the_t=0,
the_z=0,
position_x='',#x=0 is just a place holder
position_y='',#y=0 is just a place holder
position_z='',#Z=0 is just a place holder
exposure_time='',#0 is just a place holder
position_x_unit='',#mm is just a place holder
position_y_unit=''#mm is just a place holder
)
for ch in range(0,no_of_channels)
]

return PLANE

def CHANN_block(no_of_channels):

CHANN=[
Channel(
id=ome_types.model.simple_types.ChannelID('Channel:{x}'.format(x=ch)),
color=ome_types.model.simple_types.Color((255,255,255)),
emission_wavelength='',#place holder
excitation_wavelength='',#place holder
)
for ch in range(0,no_of_channels)
]

return CHANN

def PIXELS_block(no_of_tiles):

PIXELS=[
Pixels(
id=ome_types.model.simple_types.PixelsID('Pixels:{x}'.format(x=t)),
dimension_order=ome_types.model.pixels.DimensionOrder('XYCZT'),
size_c=no_of_channels,
size_t=1,
size_x=img_size[0],
size_y=img_size[1],
size_z=1,
type=bit_depth,
big_endian=False,
channels=template_chann_block,
interleaved=False,
physical_size_x=pixel_size,
physical_size_x_unit=pixel_units,
physical_size_y=pixel_size,
physical_size_y_unit=pixel_units,
physical_size_z=1.0,
planes=template_plane_block,
significant_bits=sig_bits,
tiff_data_blocks=template_tiffdata_block
)
for t in range(0,no_of_tiles)
]

return PIXELS

def IMAGE_block(no_of_tiles):

IMAGE=[
Image(
id=ome_types.model.simple_types.ImageID('Image:{x}'.format(x=t)),
pixels=pix_block[t]
)
for t in range(0,no_of_tiles)
]

return IMAGE

def OME_block(image_block):

ome=OME()
ome.creator=" ".join([ome_types.__name__,
ome_types.__version__,
'/ python version-',
platform.python_version()
]
)
ome.images=image_block
ome.uuid=uuid4().urn
ome_xml=to_xml(ome_custom)

return ome_xml




8 changes: 4 additions & 4 deletions macsima2mc/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def info_dic(target_pattern):
template={}
template['full_path']=''
template['img_name']=''

for key in target_pattern:
pattern[key]=''
template[key]=''

return template

Expand All @@ -50,7 +50,7 @@ def macsima_pattern(version=1):
"well" : r"_W-(\d+)",
"roi" : r"_G-(\d+)",
"tile" : r"_F-(\d+)",
"exposure":r"_E-(\d+)",
"exposure_time":r"_E-(\d+)",
"marker": r"Cycle_(.*?)_",
"filter": r".*_([^_]*)_\d+bit"
}
Expand All @@ -64,7 +64,7 @@ def macsima_pattern(version=1):
"well": r"_W-(.*?\d+)",
"roi": r"_ROI-(\d+)",
"tile": r"_F-(\d+)",
"exposure": r"_EXP-(\d+(?:\.\d+)?)",
"exposure_time": r"_EXP-(\d+(?:\.\d+)?)",
"marker": r"_A-(.*?)_",
"filter": r"_D-(.*?)_"
}
Expand Down
Loading

0 comments on commit b38c168

Please sign in to comment.