1+ import pandas as pd
2+ import json
3+ import os
4+ import re
5+ import tiffile
6+ import argparse
7+ import h5py
8+
9+ def get_arguments ():
10+ """
11+ Parses and checks command line arguments, and provides an help text.
12+ Assumes 3 and returns 3 positional command line arguments:
13+ """
14+ parser = argparse .ArgumentParser (description = "Split transcripts and images according to the given ROIs" )
15+ parser .add_argument ("xenium_folder" , help = "path to the xenium folder" )
16+ parser .add_argument ("roi_folder" , help = "path to the folder with the ROI images" )
17+ parser .add_argument ("output_folder" , help = "path to the output folder" )
18+ args = parser .parse_args ()
19+ return args .xenium_folder , args .roi_folder , args .output_folder
20+
21+ if __name__ == "__main__" :
22+ xenium_folder , roi_folder , ouptut_folder = get_arguments ()
23+
24+ # Expects standard xenium filenames: https://www.10xgenomics.com/support/in-situ-gene-expression/documentation/steps/onboard-analysis/at-a-glance-xenium-output-files
25+ xenium_file_path = os .path .join (xenium_folder , "experiment.xenium" )
26+ transcript_file_path = os .path .join (xenium_folder , "transcripts.parquet" )
27+ cells_file_path = os .path .join (xenium_folder , "cells.parquet" )
28+ cell_boundaries_file_path = os .path .join (xenium_folder , "cell_boundaries.parquet" )
29+ mip_tiff_file_path = os .path .join (xenium_folder , "morphology_mip.ome.tif" )
30+ print (f"Xenium file: { xenium_file_path } " )
31+ print (f"Transcript file: { transcript_file_path } " )
32+ print (f"Cell file: { cells_file_path } " )
33+ print (f"Cell Boundary file: { cell_boundaries_file_path } " )
34+ print (f"Maximum Projection Image File: { mip_tiff_file_path } " )
35+
36+ # Reads the pixel size in um
37+ with open (xenium_file_path ) as metadata_file :
38+ d = json .load (metadata_file )
39+ pixel_size = d ["pixel_size" ]
40+
41+ # Reads the transcripts and add locations in pixels
42+ print (f"Reading Transcript file: { transcript_file_path } " )
43+ transcripts = pd .read_parquet (transcript_file_path )
44+ transcripts ["x_location_px" ] = transcripts ["x_location" ] / pixel_size
45+ transcripts ["y_location_px" ] = transcripts ["y_location" ] / pixel_size
46+ transcripts ["z_location_px" ] = transcripts ["z_location" ] / pixel_size
47+
48+ # Reads the cells and add locations in pixels
49+ print (f"Reading Cell file: { cells_file_path } " )
50+ cells = pd .read_parquet (cells_file_path )
51+ cells ["x_centroid_px" ] = cells ["x_centroid" ] / pixel_size
52+ cells ["y_centroid_px" ] = cells ["y_centroid" ] / pixel_size
53+
54+ # Reads the cells and add locations in pixels
55+ print (f"Reading Cell Boundary file: { cell_boundaries_file_path } " )
56+ cell_boundaries = pd .read_parquet (cell_boundaries_file_path )
57+ cell_boundaries ["vertex_x_px" ] = cell_boundaries ["vertex_x" ] / pixel_size
58+ cell_boundaries ["vertex_y_px" ] = cell_boundaries ["vertex_y" ] / pixel_size
59+
60+ # Reads ROIS, expects _Xnnn_Ynnnn_Wnnn_Hnnnn_ with n = 0-9
61+ # makes a lists of lists [x, y, w, h]
62+ print (f"Looking for ROIs in: { roi_folder } " )
63+ ROIs = os .listdir (roi_folder )
64+ ROIs = filter (lambda filename : re .match (".*_X[0-9]+_Y[0-9]+_W[0-9]+_H[0-9]+.*" , filename ), ROIs )
65+ ROIs = [ re .match (".*_X([0-9]+)_Y([0-9]+)_W([0-9]+)_H([0-9]+).*" , filename ).groups () for filename in ROIs ]
66+ ROIs = [list (int (n ) for n in i ) for i in ROIs ]
67+ print (f"Found ROIs: { ROIs } " )
68+
69+ # Read image
70+ print (f"Reading Maximum Projection Image File: { mip_tiff_file_path } " )
71+ image = tiffile .imread (mip_tiff_file_path )
72+
73+ os .makedirs (ouptut_folder , exist_ok = True )
74+
75+ for r in ROIs :
76+ roi_string = "X_" + str (r [0 ]) + "_Y_" + str (r [0 ]) + "_W_" + str (r [2 ]) + "_H_" + str (r [2 ])
77+ print (f"Processing: { roi_string } " )
78+
79+ sub_transcripts = transcripts .loc [(transcripts ['x_location_px' ] >= r [0 ]) & (transcripts ['x_location_px' ] <= (r [0 ] + r [2 ])) & (transcripts ['y_location_px' ] >= r [1 ]) & (transcripts ['y_location_px' ] <= (r [1 ] + r [3 ]))]
80+ sub_image = image [r [1 ] : r [1 ] + r [3 ], r [0 ] : r [0 ] + r [2 ]]
81+
82+ # We exlude all cells not fully contained in the ROI!
83+ cell_boundaries ["out" ] = (cell_boundaries ['vertex_x_px' ] >= r [0 ]) & (cell_boundaries ['vertex_x_px' ] <= (r [0 ] + r [2 ])) & (cell_boundaries ['vertex_y_px' ] >= r [1 ]) & (cell_boundaries ['vertex_y_px' ] <= (r [1 ] + r [3 ]))
84+ filter_df = cell_boundaries [["cell_id" , "out" ]].groupby ("cell_id" ).filter (lambda x : all (x ["out" ])).drop_duplicates ()
85+
86+ sub_cell_boundaries = pd .merge (cell_boundaries , filter_df ["cell_id" ], on = "cell_id" , how = "inner" )
87+ sub_cell_boundaries = sub_cell_boundaries .drop ('out' , axis = 1 )
88+ sub_cells = pd .merge (cells , filter_df ["cell_id" ], on = "cell_id" , how = "inner" )
89+
90+
91+ print (f"Writing { roi_string } " )
92+ subfolder = os .path .join (ouptut_folder , roi_string )
93+ os .makedirs (subfolder , exist_ok = True )
94+
95+ tiffile .imwrite (os .path .join (subfolder , roi_string + "-morphology_mip.ome.tif" ),data = sub_image , ome = True )
96+ sub_transcripts .to_parquet (os .path .join (subfolder , roi_string + "-transcritps.parquet" ))
97+ sub_transcripts .to_csv (os .path .join (subfolder , roi_string + "-transcritps.csv" ))
98+ sub_cells .to_parquet (os .path .join (subfolder , roi_string + "-cells.parquet" ))
99+ sub_cells .to_csv (os .path .join (subfolder , roi_string + "-cells.csv" ))
100+ sub_cell_boundaries .to_parquet (os .path .join (subfolder , roi_string + "-cell_boundaries.parquet" ))
101+ sub_cell_boundaries .to_csv (os .path .join (subfolder , roi_string + "-cell_boundaries.csv" ))
102+
0 commit comments