-
Notifications
You must be signed in to change notification settings - Fork 9
Mib 21: Add stitching script #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mlnagy
wants to merge
14
commits into
master
Choose a base branch
from
MIB-21
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
49e9c1e
MIB-20 fix with replication due to astype conversion
mlnagy dfdaafe
loosened pandas req
mlnagy 8db0a94
MIB-21 script for stitching FOVs - init
mlnagy 02556e0
fixes in script; all seems to work now; just need to test upload once…
mlnagy 8a61f0e
updated based on PR feedback: reverted pandas version; removed loop f…
mlnagy 008cc0f
updated based on PR feedbacks
mlnagy 588b854
removed need to query mibitracker if not uploading; improved uploaded…
mlnagy 9ad84fd
added nonsquare to stitch
mlnagy 64d5bf8
added use of set_image_size function
mlnagy f1d1dc6
fixes with nonsquare stitch
mlnagy 015b35b
Add argparse for script parameters
jaytarolli 13cfd06
Verify args and small changes
jaytarolli 9e4c179
Remove unused variable
jaytarolli 07d097e
Add README for the script
jaytarolli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| """ Script for creating a stitched ROI MIBItiff from a folder of FOVs | ||
| """ | ||
|
|
||
| import numpy as np | ||
| import os | ||
| from mibidata import mibi_image as mi | ||
| from mibitracker.request_helpers import MibiRequests | ||
| from collections import OrderedDict | ||
| import time | ||
| from mibidata import tiff | ||
| import json | ||
|
|
||
| MAX_TRIES = 10 | ||
|
|
||
|
|
||
| def combine_entity_by_name(roi_fov_paths, cols, rows, enforce_square): | ||
| min_col = np.min(cols) | ||
| min_row = np.min(rows) | ||
| cols=[v-min_col+1 for v in cols] | ||
| rows=[v-min_row+1 for v in rows] | ||
| w = np.max(cols) | ||
| h = np.max(rows) | ||
|
|
||
| panel = None | ||
| out_img = None | ||
| for fov_i,roi_fov_path in enumerate(roi_fov_paths): | ||
| print(fov_i+1,roi_fov_path) | ||
|
|
||
| fov_img = tiff.read(roi_fov_path) | ||
| panel = fov_img.channels | ||
| fov_img = fov_img.data | ||
|
|
||
| img_shape = list(np.shape(fov_img)) | ||
| ch_count = np.min(img_shape) | ||
| shape_2d = img_shape[:-1] | ||
|
|
||
| if out_img is None: | ||
| out_img = np.zeros((h*shape_2d[0], w*shape_2d[1], ch_count), dtype=fov_img.dtype) | ||
| out_img_shape = list(np.shape(out_img)) | ||
|
|
||
| for ch_i in range(ch_count): | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| out_img[((rows[fov_i]-1)*shape_2d[0]):(rows[fov_i]*shape_2d[0]), | ||
| ((cols[fov_i]-1)*shape_2d[1]):(cols[fov_i]*shape_2d[1]), ch_i] = fov_img[:,:,ch_i] | ||
|
|
||
| if enforce_square: | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| if out_img_shape[0] > out_img_shape[1]: | ||
| out_img = np.pad(out_img,((0,0),(0,out_img_shape[0]-out_img_shape[1]),(0,0))) | ||
| elif out_img_shape[0] < out_img_shape[1]: | ||
| out_img = np.pad(out_img,((0,out_img_shape[1]-out_img_shape[0]),(0,0),(0,0))) | ||
|
|
||
| return out_img.astype(out_img.dtype, copy=False), panel, int(max(w*shape_2d[1], h*shape_2d[0])) | ||
|
|
||
|
|
||
| def run_task(fov_paths, out_path, session_dict, mt_upload): | ||
| unique_rois = np.unique([os.path.basename(p).split("-")[-1].split(".tiff")[0].split("_")[0] for p in fov_paths]) | ||
| roi_path_groups = dict([(u,[p for p in fov_paths if u in p]) for u in unique_rois]) | ||
| mr = None | ||
| for t in range(MAX_TRIES): | ||
| try: | ||
| mr = MibiRequests(**session_dict) | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| mr = MibiRequests(**session_dict) | ||
|
|
||
| for roi,roi_fov_paths in roi_path_groups.items(): | ||
| print(roi) | ||
|
|
||
| cols = [] | ||
| rows = [] | ||
| for fov_path in roi_fov_paths: | ||
| fov_name = os.path.basename(fov_path).split("-")[-1].split(".tiff")[0] | ||
| c, r = fov_name.split("_")[1:] | ||
| cols.append(int(c[1:].lstrip("0"))) | ||
| rows.append(int(r[1:].lstrip("0"))) | ||
|
|
||
| out_img, panel, max_dim = combine_entity_by_name(roi_fov_paths, cols, rows, enforce_square=True) | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| out_img = out_img.astype(np.uint8, copy=False) | ||
|
|
||
| um_min_x, um_min_y = 999999999, 999999999 | ||
| for fov_path in roi_fov_paths: | ||
|
|
||
| json_file = os.path.dirname(fov_path)+"/"+"-".join(os.path.basename(fov_path).split("-")[:2])+"-scan-1.json" | ||
| with open(json_file) as f: | ||
| bin_json = json.load(f) | ||
| coord = bin_json["coordinates"] | ||
| if coord["x"] < um_min_x: | ||
| um_min_x = coord["x"] | ||
| if coord["y"] < um_min_y: | ||
| um_min_y = coord["y"] | ||
|
|
||
| run = os.path.basename(os.path.dirname(roi_fov_paths[0])) | ||
| fov = "FOV"+os.path.basename(roi_fov_paths[0]).split("-")[1] | ||
| print(run, fov) | ||
|
|
||
| for t in range(MAX_TRIES): | ||
| try: | ||
| try: | ||
| ref_image_id = mr.image_id(run, fov) | ||
| except: | ||
| ref_image_id = mr.image_id(run, fov.replace("V0", "V")) | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| try: | ||
| ref_image_id = mr.image_id(run, fov) | ||
| except: | ||
| ref_image_id = mr.image_id(run, fov.replace("V0", "V")) | ||
|
|
||
| for t in range(MAX_TRIES): | ||
| try: | ||
| ref_json = mr.get('images/{}/'.format(ref_image_id)).json() | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| ref_json = mr.get('images/{}/'.format(ref_image_id)).json() | ||
|
|
||
| sample_id = run+"__"+roi | ||
| f_split = ref_json['folder'].split('/') | ||
| f_split[0] = sample_id | ||
| folder_name = '/'.join(f_split[:3]) | ||
|
|
||
| px_per_u = ref_json["frame"]/ref_json["fov_size"] | ||
|
|
||
| metadata = { | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| 'run': '{}'.format(ref_json['run']['label']), | ||
| 'date': ref_json['run']['run_date'], | ||
| 'coordinates': (um_min_x, um_min_y), | ||
| 'size': max_dim/px_per_u, # issue: assumption that w==h doens't hold | ||
| 'slide': ref_json['section']['slide']['id'], | ||
| 'fov_name': sample_id, | ||
| 'frame': max_dim, # issue: assumption that w==h doens't hold | ||
| 'folder': folder_name, | ||
| 'fov_id': f_split[0], | ||
| # assumption that all fovs are same | ||
| 'dwell': ref_json['dwell_time'], | ||
| 'scans': ','.join([str(d) for d in range(ref_json['depths'])]), | ||
| # assumption that all fovs are same | ||
| 'aperture': ref_json['run']['aperture']['label'], | ||
| 'instrument': ref_json['run']['instrument']['name'], | ||
| 'tissue': ref_json['formatted_tissue'], | ||
| 'panel': ref_json['section']['panel']['name'], | ||
| 'version': 'alpha', | ||
| # assumption that all fovs are same | ||
| 'mass_offset': ref_json['mass_offset'], | ||
| # assumption that all fovs are same | ||
| 'mass_gain': ref_json['mass_gain'], | ||
| # assumption that all fovs are same | ||
| 'time_resolution': ref_json['time_bin'], | ||
| 'filename': '{}'.format(ref_json['run']['name']) | ||
| } | ||
|
|
||
| out_mibi_tiff = mi.MibiImage( | ||
| out_img, panel, datetime_format='%Y-%m-%d', **metadata) | ||
|
|
||
| f_split = out_mibi_tiff.folder.split('/') | ||
| f_split[0] = sample_id | ||
| out_mibi_tiff.set_fov_id(f_split[0], '/'.join(f_split)) | ||
|
|
||
| tiff.write(out_path, out_mibi_tiff, dtype=np.float32) | ||
| print(f"Stitched MIBItiff saved to {out_path}.") | ||
|
|
||
| if mt_upload: | ||
| for t in range(MAX_TRIES): | ||
| try: | ||
| exists = mr.get( | ||
| '/images/', params={'run__label': ref_json['run']['label'], 'number': f_split[0]}).json() | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| exists = mr.get( | ||
| '/images/', params={'run__label': ref_json['run']['label'], 'number': f_split[0]}).json() | ||
|
|
||
| full_id = exists['results'][0]['id'] if exists['count'] else None | ||
|
|
||
| if not full_id: | ||
| new_im_metadata = {} | ||
| new_im_metadata['run'] = ref_json['run']['id'] | ||
| new_im_metadata['point'] = sample_id | ||
| new_im_metadata['number'] = out_mibi_tiff.fov_id | ||
| new_im_metadata['folder'] = out_mibi_tiff.folder | ||
| new_im_metadata['fov_size'] = max_dim/px_per_u | ||
| # assumption that all fovs are same | ||
| new_im_metadata['dwell_time'] = ref_json['dwell_time'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['depths'] = ref_json['depths'] | ||
| new_im_metadata['frame'] = max_dim | ||
| # assumption that all fovs are same | ||
| new_im_metadata['time_bin'] = ref_json['time_bin'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['mass_gain'] = ref_json['mass_gain'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['mass_offset'] = ref_json['mass_offset'] | ||
| new_im_metadata['x_coord'] = int(np.round(um_min_x)) | ||
| new_im_metadata['y_coord'] = int(np.round(um_min_y)) | ||
| new_im_metadata['tissue'] = \ | ||
| ref_json['tissue'] and ref_json['tissue']['id'] | ||
| new_im_metadata['section'] = ref_json['section']['id'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['aperture'] = ref_json['aperture']['id'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['imaging_preset'] = ref_json['imaging_preset'] | ||
| # assumption that all fovs are same | ||
| new_im_metadata['lens1_voltage'] = ref_json['lens1_voltage'] | ||
|
|
||
| for t in range(MAX_TRIES): | ||
| try: | ||
| _ = mr.post('/images/', json=new_im_metadata) | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| _ = mr.post('/images/', json=new_im_metadata) | ||
|
|
||
| for t in range(MAX_TRIES): | ||
| try: | ||
| mr.upload_mibitiff(out_path, run_id=ref_json['run']['id']) | ||
| break | ||
| except: | ||
| if t < MAX_TRIES-1: | ||
| time.sleep(0.50) | ||
| else: | ||
| mr.upload_mibitiff(out_path, run_id=ref_json['run']['id']) | ||
|
|
||
| print(f"Stitched MIBItiff, {out_path} uploaded to MIBItracker.") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
|
||
| fovs_folder = "/Users/mnagy/projects/stitch_script_test/2024-04-29T10-07-46_gold_tonsil_3x3_coarse_ROI" | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| out_path = "/Users/mnagy/projects/stitch_script_test/2024-04-29T10-07-46_gold_tonsil_3x3_coarse_ROI.tiff" | ||
| cmd = f'ls {fovs_folder}/*.tiff' | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
| fov_paths = os.popen(cmd).read().strip().split("\n") | ||
| try: | ||
| fov_paths.remove("") | ||
| except: | ||
| pass | ||
|
|
||
| session_dict = { | ||
| "url": "https://mibitracker.api.ionpath.com/", # MIBItracker backend URL | ||
| "email": "", # User name | ||
| "password": "" # User password | ||
| } | ||
| mt_upload = True | ||
|
mlnagy marked this conversation as resolved.
Outdated
|
||
|
|
||
| run_task(fov_paths, out_path, session_dict, mt_upload) | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.