├── LICENSE ├── Mistic_code ├── app.py ├── code │ ├── apply_theme.py │ ├── image_tSNE_GUI │ │ ├── current_package_versions.txt │ │ ├── desc.html │ │ ├── descMontage.html │ │ ├── descSM1.html │ │ ├── main.py │ │ ├── main_old.py │ │ ├── static │ │ │ └── image_tsne_tils_all_3_rot.png │ │ └── templates │ │ │ └── index.html │ ├── mistic.sh │ ├── output_tiles │ │ └── test.txt │ └── user_inputs │ │ ├── figures │ │ └── .gitkeep │ │ └── metadata │ │ ├── CODEX │ │ ├── Marker_ids.csv │ │ └── markers.csv │ │ ├── CyCIF │ │ ├── Marker_ids.csv │ │ └── markers.csv │ │ ├── Vectra │ │ ├── Cluster_categories.csv │ │ ├── Marker_ids.csv │ │ ├── Patient_ids.csv │ │ ├── Response_categories.csv │ │ ├── Treatment_categories.csv │ │ ├── X_imagetSNE.csv │ │ └── markers.csv │ │ └── t-CyCIF │ │ ├── Marker_ids.csv │ │ └── markers.csv ├── color_scatter.html ├── image.html ├── theme.yaml └── title.html ├── Readme.md └── fig_readme ├── Figure_2.jpg └── GUI_Mistic.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 IMO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mistic_code/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Run this app with `python app.py` and 4 | # visit http://127.0.0.1:8050/ in your web browser. 5 | 6 | import dash 7 | import dash_core_components as dcc 8 | import dash_html_components as html 9 | import plotly.express as px 10 | import pandas as pd 11 | 12 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] 13 | 14 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets) 15 | 16 | # assume you have a "long-form" data frame 17 | # see https://plotly.com/python/px-arguments/ for more options 18 | df = pd.DataFrame({ 19 | "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"], 20 | "Amount": [4, 1, 2, 2, 4, 5], 21 | "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"] 22 | }) 23 | 24 | fig = px.bar(df, x="Fruit", y="Amount", color="City", barmode="group") 25 | 26 | app.layout = html.Div(children=[ 27 | html.H1(children='Hello Dash'), 28 | 29 | html.Div(children=''' 30 | Dash: A web application framework for Python. 31 | '''), 32 | 33 | dcc.Graph( 34 | id='example-graph', 35 | figure=fig 36 | ) 37 | ]) 38 | 39 | if __name__ == '__main__': 40 | app.run_server(debug=True) 41 | -------------------------------------------------------------------------------- /Mistic_code/code/apply_theme.py: -------------------------------------------------------------------------------- 1 | """ Example demonstrating how to apply a theme 2 | """ 3 | 4 | # External imports 5 | import numpy as np 6 | 7 | # Bokeh imports 8 | from bokeh.io import curdoc 9 | from bokeh.plotting import figure 10 | 11 | p = figure(width=800) 12 | props = dict(line_width=4, line_alpha=0.7) 13 | 14 | x = np.arange(-np.pi, np.pi, np.pi / 16) 15 | l0 = p.line(x, np.sin(x), color='yellow', legend_label='sin', **props) 16 | l2 = p.line(x, np.cos(x), color='red', legend_label='cos', **props) 17 | 18 | curdoc().add_root(p) 19 | curdoc().theme = 'dark_minimal' 20 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/current_package_versions.txt: -------------------------------------------------------------------------------- 1 | Package Version Latest Type 2 | ---------------------------- --------- ------------ ----- 3 | antlr4-python3-runtime 4.9.3 4.13.1 wheel 4 | anyio 4.2.0 4.4.0 wheel 5 | appnope 0.1.2 0.1.4 wheel 6 | archspec 0.2.3 0.2.4 wheel 7 | argon2-cffi 21.3.0 23.1.0 wheel 8 | asttokens 2.0.5 2.4.1 wheel 9 | attrs 23.1.0 23.2.0 wheel 10 | Babel 2.11.0 2.15.0 wheel 11 | beautifulsoup4 4.12.2 4.12.3 wheel 12 | bleach 4.1.0 6.1.0 wheel 13 | bokeh 3.4.1 3.4.2 wheel 14 | Bottleneck 1.3.7 1.4.0 wheel 15 | certifi 2024.2.2 2024.6.2 wheel 16 | comm 0.2.1 0.2.2 wheel 17 | conda-package-handling 2.2.0 2.3.0 wheel 18 | conda_package_streaming 0.9.0 0.10.0 wheel 19 | contourpy 1.2.0 1.2.1 wheel 20 | cycler 0.11.0 0.12.1 wheel 21 | debugpy 1.6.7 1.8.2 wheel 22 | dulwich 0.21.7 0.22.1 sdist 23 | exceptiongroup 1.2.0 1.2.1 wheel 24 | executing 0.8.3 2.0.1 wheel 25 | filelock 3.15.3 3.15.4 wheel 26 | fonttools 4.51.0 4.53.0 wheel 27 | fsspec 2024.6.0 2024.6.1 wheel 28 | gast 0.5.4 0.6.0 sdist 29 | grpcio 1.64.0 1.64.1 wheel 30 | idna 3.6 3.7 wheel 31 | imageio 2.34.1 2.34.2 wheel 32 | importlib_metadata 7.2.0 8.0.0 wheel 33 | ipykernel 6.28.0 6.29.5 wheel 34 | ipython 8.20.0 8.26.0 wheel 35 | ipywidgets 8.1.2 8.1.3 wheel 36 | jedi 0.18.1 0.19.1 wheel 37 | joblib 1.4.0 1.4.2 wheel 38 | json5 0.9.6 0.9.25 wheel 39 | jsonpointer 2.4 3.0.0 wheel 40 | jsonschema 4.19.2 4.22.0 wheel 41 | jsonschema-specifications 2023.7.1 2023.12.1 wheel 42 | jupyter_client 8.6.0 8.6.2 wheel 43 | jupyter_core 5.5.0 5.7.2 wheel 44 | jupyter-events 0.8.0 0.10.0 wheel 45 | jupyter-lsp 2.2.0 2.2.5 wheel 46 | jupyter_server 2.10.0 2.14.1 wheel 47 | jupyter_server_terminals 0.4.4 0.5.3 wheel 48 | jupyterlab 4.0.11 4.2.3 wheel 49 | jupyterlab-pygments 0.1.2 0.3.0 wheel 50 | jupyterlab_server 2.25.1 2.27.2 wheel 51 | jupyterlab-widgets 3.0.10 3.0.11 wheel 52 | keras 3.3.3 3.4.1 wheel 53 | keyring 24.3.1 25.2.1 wheel 54 | kiwisolver 1.4.4 1.4.5 wheel 55 | lightning 2.3.0 2.3.1 wheel 56 | lightning-utilities 0.11.2 0.11.3.post0 wheel 57 | MarkupSafe 2.1.3 2.1.5 wheel 58 | matplotlib 3.8.4 3.9.0 wheel 59 | matplotlib-inline 0.1.6 0.1.7 wheel 60 | mistune 2.0.4 3.0.2 wheel 61 | ml-dtypes 0.3.2 0.4.0 wheel 62 | nbclient 0.8.0 0.10.0 wheel 63 | nbconvert 7.10.0 7.16.4 wheel 64 | nbformat 5.9.2 5.10.4 wheel 65 | notebook 7.0.8 7.2.1 wheel 66 | notebook_shim 0.2.3 0.2.4 wheel 67 | numexpr 2.8.7 2.10.1 wheel 68 | numpy 1.26.4 2.0.0 wheel 69 | overrides 7.4.0 7.7.0 wheel 70 | packaging 24.0 24.1 wheel 71 | pandas 2.2.1 2.2.2 wheel 72 | pandocfilters 1.5.0 1.5.1 wheel 73 | parso 0.8.3 0.8.4 wheel 74 | pexpect 4.8.0 4.9.0 wheel 75 | pillow 10.3.0 10.4.0 wheel 76 | pip 24.0 24.1.1 wheel 77 | platformdirs 4.2.0 4.2.2 wheel 78 | pluggy 1.4.0 1.5.0 wheel 79 | prometheus-client 0.14.1 0.20.0 wheel 80 | prompt-toolkit 3.0.43 3.0.47 wheel 81 | protobuf 4.25.3 5.27.2 wheel 82 | psutil 5.9.0 6.0.0 wheel 83 | pydantic 2.7.4 2.8.0 wheel 84 | pydantic_core 2.18.4 2.20.1 wheel 85 | Pygments 2.15.1 2.18.0 wheel 86 | pyparsing 3.0.9 3.1.2 wheel 87 | pytorch-lightning 2.3.0 2.3.1 wheel 88 | pyzmq 25.1.2 26.0.3 wheel 89 | qtconsole 5.5.1 5.5.2 wheel 90 | rapidfuzz 3.9.3 3.9.4 wheel 91 | referencing 0.30.2 0.35.1 wheel 92 | requests 2.31.0 2.32.3 wheel 93 | rpds-py 0.10.6 0.18.1 wheel 94 | scikit-image 0.23.2 0.24.0 wheel 95 | scikit-learn 1.4.2 1.5.1 wheel 96 | scipy 1.13.0 1.14.0 wheel 97 | Send2Trash 1.8.2 1.8.3 wheel 98 | setuptools 69.5.1 70.2.0 wheel 99 | sip 6.7.12 6.8.5 wheel 100 | sniffio 1.3.0 1.3.1 wheel 101 | stack-data 0.2.0 0.6.3 wheel 102 | style 1.1.0 1.1.6 wheel 103 | tensorboard 2.16.2 2.17.0 wheel 104 | tensorflow 2.16.1 2.16.2 wheel 105 | tensorflow-io-gcs-filesystem 0.37.0 0.37.1 wheel 106 | terminado 0.17.1 0.18.1 wheel 107 | threadpoolctl 2.2.0 3.5.0 wheel 108 | tifffile 2024.5.22 2024.7.2 wheel 109 | tinycss2 1.2.1 1.3.0 wheel 110 | tornado 6.3.3 6.4.1 wheel 111 | tqdm 4.66.2 4.66.4 wheel 112 | traitlets 5.7.1 5.14.3 wheel 113 | trove-classifiers 2024.5.22 2024.7.2 wheel 114 | truststore 0.8.0 0.9.1 wheel 115 | typing_extensions 4.11.0 4.12.2 wheel 116 | tzdata 2023.3 2024.1 wheel 117 | urllib3 2.2.1 2.2.2 wheel 118 | virtualenv 20.26.2 20.26.3 wheel 119 | wcwidth 0.2.5 0.2.13 wheel 120 | widgetsnbextension 4.0.10 4.0.11 wheel 121 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/desc.html: -------------------------------------------------------------------------------- 1 | 43 | 44 |

Mistic: Image t-SNE viewer for multiplexed images

45 | 46 |

47 | Interact with the widgets below to plot either a stack montage for all markers of a single multiplxed image, or multiple multiplexed images based on a subset of markers. 48 | There are two canvases: the image tSNE canvas to the right with the multiplexed images and the live canvases showing the tSNE scatter plots based on image metadata. 49 | Hover over the tSNE dots to get more information about each image. 50 |

51 | 52 |

53 | Imaging Technique 54 |

55 | 56 |
57 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/descMontage.html: -------------------------------------------------------------------------------- 1 | 44 | 45 |

46 | 47 |

48 | Or 49 |

50 | 51 |

52 | Multiple multiplexed images 53 |

54 | 55 |
56 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/descSM1.html: -------------------------------------------------------------------------------- 1 | 41 | 42 | 43 |

44 | Single multiplexed image 45 |

46 | 47 |
48 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/main.py: -------------------------------------------------------------------------------- 1 | ### Code for Mistic 2 | ### Image t-SNE viewer for multiplexed images 3 | ### 4 | ### code author Sandhya Prabhakaran 5 | ### 6 | ### Revision history 7 | ### FoV code ### 8 | ### Jan 29th 2021 9 | ### 25th June 2021 10 | ### github version 11 | ### 19th Jan 2022 12 | ### added Patient id live canvas 13 | ### 26th Jan 2022 ### 14 | ### merging the stack montage code 15 | ### prior version is main_rollback. 16 | ### 14th Feb 2022 17 | ### adding codex, tcycif, vectra option 18 | ### prior version is main_rollback_1 19 | ### 14th mar 2022 20 | ### added the user tsne as well 'arrange in rows' (seeall) tsne generation code 21 | 22 | ## 20th Feb 2022 23 | ## this is the latest main.py, with dates and comments removed 24 | ## for github upload 25 | 26 | ## 8th April 2022 27 | ## 2nd rebuttal updates 28 | ## tsne and dpmm code added 29 | 30 | #### 21st April 2022 31 | #### CyCIF files 32 | ## code cleanup for github 33 | 34 | 35 | ### 27th June 2024 ### 36 | ### Package compatibility updates 37 | 38 | 39 | 40 | import os 41 | import sys 42 | import random 43 | import warnings 44 | 45 | import numpy as np 46 | import pandas as pd 47 | 48 | import matplotlib 49 | import matplotlib.pyplot as plt 50 | import matplotlib.patches as mpatches 51 | 52 | from matplotlib.figure import Figure 53 | 54 | from matplotlib import cm 55 | 56 | from scipy import ndimage 57 | 58 | from sklearn.manifold import TSNE 59 | 60 | import phenograph as pg 61 | from scipy.stats import zscore 62 | 63 | from skimage.color import rgb2gray 64 | 65 | from matplotlib.patches import Polygon 66 | 67 | from skimage import io 68 | 69 | from skimage import data 70 | from skimage.filters import threshold_otsu 71 | from skimage.segmentation import clear_border 72 | from skimage.measure import label, regionprops 73 | from skimage.morphology import closing, square 74 | from skimage.color import label2rgb 75 | from skimage.io import imread, imshow, imread_collection, concatenate_images 76 | from skimage.transform import resize 77 | from skimage.morphology import label 78 | 79 | import seaborn as sns 80 | 81 | import scipy.spatial 82 | 83 | from scipy.spatial import distance 84 | 85 | 86 | import seaborn as sns 87 | 88 | 89 | import matplotlib.image as mpimg 90 | 91 | 92 | import matplotlib.colors as colors 93 | 94 | from matplotlib import colors as mcolors 95 | 96 | colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS) 97 | 98 | from PIL import Image, ImageOps 99 | 100 | from bokeh.layouts import column, row 101 | from bokeh.models import (Select, Button) 102 | from bokeh.palettes import Spectral5 103 | from bokeh.plotting import curdoc, figure 104 | from bokeh.models.widgets import Div 105 | from bokeh.layouts import column, layout 106 | 107 | from bokeh.models import (HoverTool, ColumnDataSource) 108 | from bokeh.models.widgets import RadioButtonGroup 109 | from bokeh.models import CheckboxGroup 110 | from bokeh.models import CustomJS 111 | from bokeh.models import BoxSelectTool 112 | from bokeh.themes import Theme 113 | 114 | from bokeh.models.layouts import TabPanel, Tabs #2024 115 | from bokeh.io import output_file, show 116 | 117 | 118 | ## 119 | import skimage.io as io 120 | import tifffile 121 | from PIL import TiffImagePlugin 122 | 123 | from sklearn.mixture import BayesianGaussianMixture 124 | 125 | 126 | 127 | ### image-tSNE 128 | 129 | width = 5000 130 | height = 5000 131 | max_dim = 500 132 | tw =1200 133 | th = 900 134 | 135 | full_image_1 = Image.new('RGBA', (width, height)) 136 | 137 | 138 | 139 | ############################################################## 140 | 141 | ################## Function definitions ###################### 142 | 143 | ############################################################## 144 | 145 | 146 | ####################################################################### 147 | ##### get_cell_coords(), get_neighbours(), point_valid(), #### 148 | ##### get_point() are functions to generate random points #### 149 | ##### functions modified from: #### 150 | ##### https://scipython.com/blog/poisson-disc-sampling-in-python/ #### 151 | ####################################################################### 152 | 153 | 154 | 155 | def get_cell_coords(pt,a): 156 | """Get the coordinates of the cell that pt = (x,y) falls in.""" 157 | 158 | return int(pt[0] // a), int(pt[1] // a) 159 | 160 | def get_neighbours(coords,nx,ny,cells): 161 | """Return the indexes of points in cells neighbouring cell at coords. 162 | For the cell at coords = (x,y), return the indexes of points in the cells 163 | with neighbouring coordinates illustrated below: ie those cells that could 164 | contain points closer than r. 165 | ooo 166 | ooooo 167 | ooXoo 168 | ooooo 169 | ooo 170 | """ 171 | 172 | dxdy = [(-1,-2),(0,-2),(1,-2),(-2,-1),(-1,-1),(0,-1),(1,-1),(2,-1), 173 | (-2,0),(-1,0),(1,0),(2,0),(-2,1),(-1,1),(0,1),(1,1),(2,1), 174 | (-1,2),(0,2),(1,2),(0,0)] 175 | neighbours = [] 176 | for dx, dy in dxdy: 177 | neighbour_coords = coords[0] + dx, coords[1] + dy 178 | if not (0 <= neighbour_coords[0] < nx and 179 | 0 <= neighbour_coords[1] < ny): 180 | # We're off the grid: no neighbours here. 181 | continue 182 | neighbour_cell = cells[neighbour_coords] 183 | if neighbour_cell is not None: 184 | # This cell is occupied: store this index of the contained point. 185 | neighbours.append(neighbour_cell) 186 | return neighbours 187 | 188 | def point_valid(pt,a,nx,ny,cells,samples,r): 189 | """Is pt a valid point to emit as a sample? 190 | It must be no closer than r from any other point: check the cells in its 191 | immediate neighbourhood. 192 | """ 193 | 194 | cell_coords = get_cell_coords(pt,a) 195 | for idx in get_neighbours(cell_coords,nx,ny,cells): 196 | nearby_pt = samples[idx] 197 | # Squared distance between or candidate point, pt, and this nearby_pt. 198 | distance2 = (nearby_pt[0]-pt[0])**2 + (nearby_pt[1]-pt[1])**2 199 | if distance2 < r**2: 200 | # The points are too close, so pt is not a candidate. 201 | return False 202 | # All points tested: if we're here, pt is valid 203 | return True 204 | 205 | def get_point(k, refpt,r,a,nx,ny,cells,samples): 206 | """Try to find a candidate point relative to refpt to emit in the sample. 207 | We draw up to k points from the annulus of inner radius r, outer radius 2r 208 | around the reference point, refpt. If none of them are suitable (because 209 | they're too close to existing points in the sample), return False. 210 | Otherwise, return the pt. 211 | """ 212 | i = 0 213 | while i < k: 214 | rho, theta = np.random.uniform(r, 2*r), np.random.uniform(0, 2*np.pi) 215 | pt = refpt[0] + rho*np.cos(theta), refpt[1] + rho*np.sin(theta) 216 | if not (0 <= pt[0] < width and 0 <= pt[1] < height): 217 | # This point falls outside the domain, so try again. 218 | continue 219 | if point_valid(pt,a,nx,ny,cells,samples,r): 220 | return pt 221 | i += 1 222 | # We failed to find a suitable point in the vicinity of refpt. 223 | return False 224 | 225 | ############################################################### 226 | ### draw_tSNE_scatter() #### 227 | ### a) creates the metadata-based tSNE scatter plots #### 228 | ### b) populates the hover functionality for each tSNE dot #### 229 | ############################################################### 230 | 231 | def draw_tSNE_scatter(tsne1, file_name_hover,cluster_ms_list ): 232 | tsne=np.asarray(tsne1) 233 | 234 | source = ColumnDataSource(data=dict( 235 | x=tsne[:,0], 236 | y=tsne[:,1], 237 | pat_list = pat_ind_list, 238 | res_list = resp_list, 239 | fov_list = pat_fov_list, 240 | color_vec_list = color_vec, 241 | tx_list = tx_list, 242 | color_vec_tx_list = color_vec_tx, 243 | clust_asgn_list = clust_asgn_list, 244 | color_vec_clasgn_list = color_vec_clasgn, 245 | cluster_anno_list = cluster_anno_list, 246 | cluster_ms_list = cluster_ms_list, 247 | cluster_pat_list = cluster_pat_list, 248 | color_vec_patid_list = color_vec_patid, 249 | file_name_hover_list = file_name_hover 250 | #legend_p11 = legend_p1 251 | )) 252 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select," 253 | 254 | TOOLTIPS = [ 255 | ("index", "$index"), 256 | ("(x,y)", "($x, $y)"), 257 | ("Pat_id", "@pat_list"), 258 | ("Response", "@res_list"), 259 | ("Treatment", "@tx_list"), 260 | ("Cluster id", "@clust_asgn_list"), 261 | ("Channel", "@cluster_ms_list"), 262 | ("Thumbnail", "@file_name_hover_list"), 263 | ("FoV","@fov_list") 264 | ] 265 | 266 | 267 | 268 | #2024: legend to legend_label; plot_width to width, plot_height to height 269 | p1 = figure(width=400, height=400, tooltips=TOOLTIPS,tools = TOOLS, 270 | title="Patient response") 271 | 272 | p1.scatter('x', 'y', size=10, source=source, legend_label= 'res_list', color = 'color_vec_list',fill_alpha=0.6) 273 | p1.legend.location = "bottom_left" 274 | 275 | 276 | p2 = figure(width=400, height=400, tooltips=TOOLTIPS,tools = TOOLS, 277 | title="Treatment category") 278 | p2.scatter('x', 'y', size=10, source=source, legend_label= 'tx_list', color = 'color_vec_tx_list',fill_alpha=0.6) 279 | p2.legend.location = "bottom_left" 280 | 281 | 282 | p3 = figure(width=400, height=400, tooltips=TOOLTIPS,tools = TOOLS, 283 | title="Cluster annotations") 284 | p3.scatter('x', 'y', size=10, source=source, legend_label= 'cluster_anno_list', color = 'color_vec_clasgn_list',fill_alpha=0.6) 285 | p3.legend.location = "bottom_left" 286 | 287 | 288 | 289 | p4 = figure(width=400, height=400, tooltips=TOOLTIPS,tools = TOOLS, 290 | title="Patient id") 291 | p4.scatter('x', 'y', size=10, source=source, legend_label= 'cluster_pat_list', color = 'color_vec_patid_list',fill_alpha=0.6) 292 | p4.legend.location = "bottom_left" 293 | 294 | 295 | return ([p1,p2,p3,p4, source]) 296 | 297 | 298 | 299 | 300 | 301 | ############################################################################################## 302 | ### generate_stack_montage() #### 303 | ### a) reads in and processes the image channels #### 304 | ### b) generates evenly-spaced points on the static canvas to arrange the images in rows #### 305 | ### c) generates thumbnails, and pastes these onto the static canvas #### 306 | ### d) stores the thumbnails in the output folder #### 307 | ### e) updates the hover tool with thumbnail paths, marker names and metadata #### 308 | ############################################################################################## 309 | 310 | 311 | ################## 312 | def generate_stack_montage(chk_box_marker_sm, rb_imtech_val, LABELS_MARKERS): 313 | 314 | 315 | 316 | full_image = Image.new('RGBA', (width, height)) 317 | size = [256,256] 318 | 319 | file_name_hover = [] 320 | file_name_hover_list = [] 321 | 322 | # Choose up to k points around each reference point as candidates for a new 323 | # sample point 324 | k = 10 325 | 326 | # Minimum distance between samples 327 | r = 2 328 | 329 | width_1, height_1 = 20, 22 330 | 331 | print('Arrange images side-by-side') 332 | 333 | # Cell side length 334 | a = r/np.sqrt(2) 335 | # Number of cells in the x- and y-directions of the grid 336 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 337 | 338 | # A list of coordinates in the grid of cells 339 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 340 | 341 | 342 | #logic to get grid points 343 | m = np.int64(np.floor(nx*ny/num_images)) #2024 np.int to int64 344 | 345 | 346 | row_needed = [] 347 | def multiples(m, num_images): 348 | for i in range(num_images): 349 | row_needed.append(i*m) 350 | 351 | multiples(m,num_images) 352 | 353 | 354 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 355 | 356 | print(type(select_coords)) 357 | ################ 358 | 359 | #tsne = np.asarray(coords_list) 360 | tsne = select_coords 361 | df_Xtsne = pd.DataFrame(tsne) 362 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_sm.csv'), header=None, index=None) 363 | 364 | 365 | 366 | # save the tSNE points to bve read later on irrespective of whoch option was chosen 367 | df_Xtsne_touse = pd.DataFrame(tsne) 368 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse_sm.csv'), header=None, index=None) 369 | 370 | tx, ty = tsne[:,0], tsne[:,1] 371 | 372 | tx[tx ==0] = 0.2 373 | ty[ty ==0] = 0.1 374 | 375 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 376 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 377 | print('###tx') 378 | print(tx) 379 | print(ty) 380 | inds = range(len(marker_image_list)) 381 | 382 | N = len(inds) 383 | 384 | 385 | for k in range(len(inds)): 386 | 387 | image_all_2 = [] 388 | image_all_1 = [] 389 | 390 | im = Image.open(marker_image_list[inds[k]]) 391 | 392 | 393 | for m in range(1): 394 | image = im 395 | 396 | median_filtered = scipy.ndimage.median_filter(image, size=1) 397 | image_all_2.append(median_filtered) 398 | 399 | 400 | # apply threshold 401 | 402 | thresh = threshold_otsu(image_all_2[m]) 403 | 404 | 405 | 406 | 407 | print('thresh is: ', str(thresh)) 408 | 409 | bw = closing(image > thresh)# 2024, square(1)) 410 | 411 | # remove artifacts connected to image border 412 | cleared_imtsne = clear_border(bw) 413 | 414 | 415 | image_all_1.append(cleared_imtsne*100) 416 | 417 | 418 | tl = sum(image_all_2) #2024 419 | 420 | tl=tl[0,0:tl.shape[1],0:tl.shape[2]] #2024 421 | 422 | 423 | 424 | if (rb_imtech_val ==0): #vectra 425 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) 426 | 427 | elif (rb_imtech_val ==1): #t-CyCIF 428 | tile_1 = Image.fromarray(np.uint8(cm.hsv(tl)*500)) 429 | elif (rb_imtech_val ==2): # CODEX 430 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 431 | elif (rb_imtech_val ==3): # CyCIF 432 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 433 | 434 | old_size = tile_1.size 435 | 436 | 437 | 438 | 439 | 440 | new_size = (old_size[0]+1, old_size[1]+1) 441 | new_im = Image.new("RGB", new_size,color='black') 442 | 443 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 444 | 445 | 446 | 447 | file_name = os.path.join(path_wd+'/output_tiles/sm_image_tsne_tils'+str(k)+'.png') 448 | 449 | file_name_hover.append('sm_image_tsne_tils'+str(k)+'.png') 450 | 451 | new_im.save(file_name) 452 | 453 | # Read Images 454 | 455 | 456 | tile_2 = Image.open(file_name) 457 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 458 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 459 | 460 | full_image.paste(tile_2, (int((width-max_dim)*ty[k]), int((height-max_dim)*tx[k])),mask=tile_2.convert('RGBA')) 461 | 462 | matplotlib.pyplot.figure(figsize = (25,20)) 463 | plt.imshow(full_image) 464 | plt.axis('off') 465 | 466 | 467 | 468 | 469 | 470 | k = random.randint(2,500) 471 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/sm_image_tsne_tils_all_'+str(k)+'.png') 472 | full_image.save(file_name) 473 | 474 | 475 | 476 | rotated_img = full_image.rotate(90) 477 | file_name_rot = os.path.join(path_wd+'/image_tSNE_GUI/static/sm_image_tsne_tils_all_'+str(k)+'_rot.png') 478 | 479 | 480 | rotated_img.save(file_name_rot) 481 | 482 | return([file_name_rot,tsne, file_name_hover]) 483 | 484 | 485 | 486 | ########################################################################################################### 487 | ### generate_image_tSNE() ######## 488 | ### ######## 489 | ### a) reads in and pre-processes the images ######## 490 | ### b) generates random points or evenly-spaced points on the static canvas to arrange the images ######## 491 | ### in rows or reads in the user-provided tSNE ######## 492 | ### c) generates thumbnails based on border choices, pastes the thumbnails onto the static canvas ######## 493 | ### d) stores the thumbnails in the output folder ######## 494 | ### e) updates the hover tool with thumbnail paths ######## 495 | ### f) shuffle or no shuffle option is handled in this function where images are randomly shuffled ######## 496 | ########################################################################################################### 497 | 498 | def generate_image_tSNE(chk_box_marker,rb_val,rb_rs_val,rb_shf_val, rb_imtech_val, mc, wc, LABELS_MARKERS): 499 | full_image = Image.new('RGBA', (width, height)) 500 | size = [256,256] 501 | 502 | file_name_hover = [] 503 | file_name_hover_list = [] 504 | 505 | if (rb_shf_val == 0): # no shuffle option 506 | 507 | 508 | if(rb_rs_val==1): 509 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), index_col=None, header= None) 510 | print('usertsne') 511 | elif(rb_rs_val==0): 512 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 513 | print('origtsne') 514 | 515 | elif(rb_rs_val==2): 516 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), index_col=None, header= None) 517 | print('seealltsne') 518 | 519 | tsne = np.array(df_Xtsne) 520 | 521 | ''' 522 | #create the random tsne projections 523 | if (rb_rs_val==1): 524 | # Choose up to k points around each reference point as candidates for a new 525 | # sample point 526 | k = 10 527 | 528 | # Minimum distance between samples 529 | r = 1.7 530 | 531 | width_1, height_1 = 20, 22 532 | 533 | print('Generating random co-ordinates') 534 | 535 | # Cell side length 536 | a = r/np.sqrt(2) 537 | # Number of cells in the x- and y-directions of the grid 538 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 539 | 540 | # A list of coordinates in the grid of cells 541 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 542 | # Initilalize the dictionary of cells: each key is a cell's coordinates, the 543 | # corresponding value is the index of that cell's point's coordinates in the 544 | # samples list (or None if the cell is empty). 545 | cells = {coords: None for coords in coords_list} 546 | 547 | 548 | 549 | # Pick a random point to start with. 550 | pt = (np.random.uniform(0, width_1), np.random.uniform(0, height_1)) 551 | samples = [pt] 552 | # Our first sample is indexed at 0 in the samples list... 553 | cells[get_cell_coords(pt,a)] = 0 554 | # ... and it is active, in the sense that we're going to look for more points 555 | # in its neighbourhood. 556 | active = [0] 557 | 558 | nsamples = 1 559 | # As long as there are points in the active list, keep trying to find samples. 560 | while (nsamples < num_images): #active: 561 | # choose a random "reference" point from the active list. 562 | idx = np.random.choice(active) 563 | refpt = samples[idx] 564 | # Try to pick a new point relative to the reference point. 565 | pt = get_point(k, refpt,r,a,nx,ny,cells,samples) 566 | if pt: 567 | # Point pt is valid: add it to the samples list and mark it as active 568 | samples.append(pt) 569 | nsamples += 1 570 | active.append(len(samples)-1) 571 | cells[get_cell_coords(pt,a)] = len(samples) - 1 572 | print('nsamples is: ',str(nsamples)) 573 | else: 574 | # We had to give up looking for valid points near refpt, so remove it 575 | # from the list of "active" points. 576 | active.remove(idx) 577 | 578 | tsne = np.asarray(samples) 579 | df_Xtsne = pd.DataFrame(tsne) 580 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), header=None, index=None) 581 | 582 | 583 | elif(rb_rs_val==0): 584 | 585 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 586 | df_Xtsne.shape 587 | tsne = np.array(df_Xtsne ) 588 | 589 | elif(rb_rs_val==2): 590 | 591 | # Choose up to k points around each reference point as candidates for a new 592 | # sample point 593 | k = 10 594 | 595 | # Minimum distance between samples 596 | r = 2 597 | 598 | width_1, height_1 = 20, 22 599 | 600 | print('Arrange images side-by-side') 601 | 602 | # Cell side length 603 | a = r/np.sqrt(2) 604 | # Number of cells in the x- and y-directions of the grid 605 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 606 | 607 | # A list of coordinates in the grid of cells 608 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 609 | 610 | 611 | #logic to get grid points 612 | m = np.int(np.floor(nx*ny/num_images)) 613 | 614 | 615 | row_needed = [] 616 | def multiples(m, num_images): 617 | for i in range(num_images): 618 | row_needed.append(i*m) 619 | 620 | multiples(m,num_images) 621 | 622 | 623 | 624 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 625 | 626 | print(type(select_coords)) 627 | ################ 628 | 629 | #tsne = np.asarray(coords_list) 630 | tsne = select_coords 631 | df_Xtsne = pd.DataFrame(tsne) 632 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), header=None, index=None) 633 | 634 | 635 | ''' 636 | # save the tSNE points to bve read later on irrespective of whoch option was chosen 637 | df_Xtsne_touse = pd.DataFrame(tsne) 638 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse.csv'), header=None, index=None) 639 | 640 | tx, ty = tsne[:,0], tsne[:,1] 641 | 642 | tx[tx ==0] = 0.2 643 | ty[ty ==0] = 0.1 644 | 645 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 646 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 647 | 648 | 649 | 650 | ##identify the markers 651 | mm = np.asarray(LABELS_MARKERS)[chk_box_marker] 652 | 653 | tiles = [] 654 | 655 | 656 | marker_choice = np.array(mc)[chk_box_marker] 657 | weight_choice = np.array(wc)[chk_box_marker] 658 | 659 | 660 | 661 | inds = range(len(marker_image_list)) 662 | 663 | N = len(inds) 664 | 665 | 666 | for k in range(len(inds)): 667 | 668 | image_all_2 = [] 669 | image_all_1 = [] 670 | 671 | 672 | 673 | 674 | im = tifffile.imread(marker_image_list[inds[k]]) 675 | if (rb_imtech_val ==2): #for codex 676 | im = im.reshape(64,5040,9408) 677 | 678 | 679 | 680 | for m in range(len(marker_choice)): 681 | 682 | print('############marker_choice:',str (marker_choice)) #2024 683 | image = im[marker_choice[m]] 684 | 685 | median_filtered = scipy.ndimage.median_filter(image, size=1) 686 | image_all_2.append(median_filtered) 687 | 688 | 689 | # apply threshold 690 | 691 | thresh = threshold_otsu(image_all_2[m]) 692 | 693 | 694 | 695 | 696 | print('thresh is: ', str(thresh)) 697 | 698 | bw = closing(image > thresh)#, square(1)) #2024 699 | 700 | # remove artifacts connected to image border 701 | cleared_imtsne = clear_border(bw) 702 | 703 | 704 | image_all_1.append(cleared_imtsne *weight_choice[m]) #2024 705 | 706 | tl = sum(image_all_2) #2024 707 | ##2024 708 | print('$$$$$$$$') 709 | print(len(tl)) 710 | print(tl.shape) 711 | print(np.squeeze(tl).shape) 712 | #tl=np.squeeze(tl) 713 | tl=tl[0,0:tl.shape[1],0:tl.shape[2]] #2024 714 | ## 715 | 716 | 717 | if (rb_imtech_val ==0): #vectra 718 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) #2024 commented this added below 719 | #tile_1 = Image.fromarray(np.uint8((tl)*255)) #2024 commented this added below 720 | #tile_1 = Image.fromarray((tl).astype(np.uint8),'RGB') 721 | #im = Image.fromarray((x * 255).astype(np.uint8)) 722 | 723 | elif (rb_imtech_val ==1): #t-CyCIF 724 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 725 | elif (rb_imtech_val ==2): # CODEX 726 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 727 | elif (rb_imtech_val ==3): # CyCIF 728 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 729 | 730 | old_size = tile_1.size 731 | 732 | 733 | 734 | 735 | if(rb_val==0): 736 | new_size = (old_size[0]+1, old_size[1]+1) 737 | new_im = Image.new("RGB", new_size,color='black') 738 | 739 | elif(rb_val==1): # for the border #response based 740 | new_size = (old_size[0]+50, old_size[1]+50) 741 | 742 | 743 | 744 | new_im = Image.new("RGB", new_size,color_vec[inds[k]])## color='yellow') 745 | 746 | elif(rb_val==2): # for the border #treatment based 747 | new_size = (old_size[0]+50, old_size[1]+50) 748 | 749 | 750 | new_im = Image.new("RGB", new_size,color_vec_tx[inds[k]])# color='yellow') 751 | 752 | elif(rb_val==3): # for the border #cluster based 753 | new_size = (old_size[0]+50, old_size[1]+50) 754 | 755 | 756 | new_im = Image.new("RGB", new_size,color_vec_clasgn[inds[k]])# colours_58[cx])# color='yellow') 757 | 758 | 759 | elif(rb_val==4): # for the border #patient id based 760 | new_size = (old_size[0]+50, old_size[1]+50) 761 | 762 | 763 | new_im = Image.new("RGB", new_size, color_vec_patid[inds[k]])# color='yellow') 764 | 765 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 766 | 767 | 768 | 769 | file_name = os.path.join(path_wd+'/output_tiles/image_tsne_tils'+str(k)+'.png') 770 | 771 | file_name_hover.append('image_tsne_tils'+str(k)+'.png') 772 | 773 | new_im.save(file_name) 774 | 775 | # Read Images 776 | 777 | 778 | tile_2 = Image.open(file_name) 779 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 780 | #tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 781 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.Resampling.LANCZOS) #2024 Resampling.LANCZOS for ANTIALIAS #commented antialias on 9th Feb 2021 782 | 783 | full_image.paste(tile_2, (int((width-max_dim)*ty[k]), int((height-max_dim)*tx[k])),mask=tile_2.convert('RGBA')) 784 | 785 | 786 | 787 | matplotlib.pyplot.figure(figsize = (25,20)) 788 | plt.imshow(full_image) 789 | plt.axis('off') 790 | 791 | 792 | 793 | 794 | k = random.randint(2,500) 795 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k)+'.png') 796 | full_image.save(file_name) 797 | 798 | 799 | 800 | rotated_img = full_image.rotate(90) 801 | file_name_rot = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k)+'_rot.png') 802 | 803 | 804 | rotated_img.save(file_name_rot) 805 | 806 | 807 | 808 | 809 | ##code for reshuffling 810 | 811 | elif (rb_shf_val==1): # shuffle images 812 | 813 | #pick the tsne file to start with 814 | if(rb_rs_val==1): 815 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), index_col=None, header= None) 816 | print('usertsne') 817 | elif(rb_rs_val==0): 818 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 819 | print('origtsne') 820 | 821 | elif(rb_rs_val==2): 822 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), index_col=None, header= None) 823 | print('seealltsne') 824 | 825 | tsne = np.array(df_Xtsne) 826 | 827 | 828 | 829 | # save the tSNE points to be read later on, irrespective of which option was chosen 830 | df_Xtsne_touse = pd.DataFrame(tsne) 831 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse.csv'), header=None, index=None) 832 | 833 | 834 | tx, ty = tsne[:,0], tsne[:,1] 835 | 836 | tx[tx ==0] = 0.2 837 | ty[ty ==0] = 0.1 838 | 839 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 840 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 841 | 842 | full_image = Image.new('RGBA', (width, height)) 843 | 844 | size = [256,256] 845 | 846 | 847 | ##identify the markers 848 | mm = np.asarray(LABELS_MARKERS)[chk_box_marker] 849 | 850 | tiles = [] 851 | 852 | 853 | marker_choice = np.array(mc)[chk_box_marker] 854 | weight_choice = np.array(wc)[chk_box_marker] 855 | 856 | inds = range(len(marker_image_list)) 857 | 858 | N = len(inds) 859 | shf_N = np.array(range(N)) 860 | random.shuffle(shf_N) 861 | print("#############shuffle file order###") 862 | print(shf_N) 863 | count_file = 0 864 | 865 | for k in range(N): 866 | 867 | image_all_2 = [] 868 | image_all_1 = [] 869 | 870 | 871 | 872 | 873 | im = tifffile.imread(marker_image_list[shf_N[k]]) 874 | if (rb_imtech_val ==2): #for codex 875 | im = im.reshape(64,5040,9408) ## 876 | 877 | for m in range(len(marker_choice)): 878 | image = im[marker_choice[m]] 879 | 880 | median_filtered = scipy.ndimage.median_filter(image, size=1) 881 | image_all_2.append(median_filtered) 882 | 883 | 884 | 885 | # apply threshold 886 | 887 | thresh = threshold_otsu(image_all_2[m]) 888 | 889 | 890 | print(thresh) 891 | 892 | bw = closing(image > thresh)# 2024, square(1)) 893 | 894 | # remove artifacts connected to image border 895 | cleared_imtsne = clear_border(bw) 896 | 897 | 898 | image_all_1.append(cleared_imtsne*weight_choice[m]) 899 | 900 | tl = sum(image_all_2) #2024 901 | 902 | tl=tl[0,0:tl.shape[1],0:tl.shape[2]] #2024 903 | 904 | 905 | if (rb_imtech_val ==0): #vectra 906 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) 907 | 908 | elif (rb_imtech_val ==1): #t-CyCIF 909 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 910 | elif (rb_imtech_val ==2): # CODEX 911 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 912 | elif (rb_imtech_val ==3): # CyCIF 913 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 914 | 915 | old_size = tile_1.size 916 | 917 | print(pat_ind_list[shf_N[k]]) 918 | 919 | 920 | if(rb_val==0): 921 | new_size = (old_size[0]+1, old_size[1]+1) 922 | new_im = Image.new("RGB", new_size,color='black') 923 | 924 | elif(rb_val==1): # for the border #response based 925 | new_size = (old_size[0]+50, old_size[1]+50) 926 | 927 | 928 | 929 | 930 | new_im = Image.new("RGB", new_size,color_vec[shf_N[k]])# color='yellow') 931 | 932 | elif(rb_val==2): # for the border #treatment based 933 | new_size = (old_size[0]+50, old_size[1]+50) 934 | 935 | new_im = Image.new("RGB", new_size,color_vec_tx[shf_N[k]])# color='yellow') 936 | 937 | elif(rb_val==3): # for the border #cluster based 938 | new_size = (old_size[0]+50, old_size[1]+50) 939 | 940 | 941 | new_im = Image.new("RGB", new_size,color_vec_clasgn[shf_N[k]])# color='yellow') 942 | 943 | 944 | elif(rb_val==4): # for the border #patient id based 945 | new_size = (old_size[0]+50, old_size[1]+50) 946 | 947 | 948 | new_im = Image.new("RGB", new_size,color_vec_patid[shf_N[k]])# color='yellow') 949 | 950 | 951 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 952 | 953 | 954 | count_file = np.int64(np.array(np.where(shf_N[k]==shf_N)).flatten()) #2024 np.int to int64 955 | 956 | file_name = os.path.join(path_wd+'/output_tiles/image_tsne_tils'+str(shf_N[k])+'.png') 957 | 958 | file_name_hover.append('image_tsne_tils'+str(shf_N[k])+'.png') 959 | 960 | 961 | new_im.save(file_name) 962 | 963 | 964 | 965 | # Read Images 966 | 967 | 968 | tile_2 = Image.open(file_name) 969 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 970 | #tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 971 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.Resampling.LANCZOS) # 2024 Resampling.LANCZOS for ANTIALIAS #commented antialias on 9th Feb 2021 972 | 973 | 974 | full_image.paste(tile_2, (int((width-max_dim)*ty[shf_N[k]]), int((height-max_dim)*tx[shf_N[k]])),mask=tile_2.convert('RGBA')) 975 | 976 | 977 | matplotlib.pyplot.figure(figsize = (25,20)) 978 | plt.imshow(full_image) 979 | plt.axis('off') 980 | 981 | 982 | 983 | ## order filenamehover list before sending it out to the live panels 984 | so = np.argsort(shf_N) 985 | file_name_hover_np = np.array(file_name_hover) 986 | sorted_file_name_hover = file_name_hover_np[so] 987 | file_name_hover = sorted_file_name_hover.tolist() 988 | 989 | 990 | k1 = random.randint(2,500) 991 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k1)+'.png') 992 | 993 | full_image.save(file_name) 994 | 995 | 996 | rotated_img = full_image.rotate(90) 997 | file_name_rot = file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k1)+'_rot.png') 998 | 999 | 1000 | 1001 | rotated_img.save(file_name_rot) 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | return([file_name_rot,tsne, file_name_hover]) 1011 | 1012 | 1013 | ############################################################################### 1014 | ### button_callback() #### 1015 | ### a) Calls the create_figure() that collects user inputs from the GUI #### 1016 | ### b) draw_tSNE_scatter() generates the tSNE plots for the live canvases #### 1017 | ### b) reads in the user-provided tSNE #### 1018 | ### c) generates thumbnails, and pastes these onto the static canvas #### 1019 | ### d) stores the thumbnails in the output folder #### 1020 | ### e) updates the hover tool with thumbnail paths #### 1021 | ############################################################################### 1022 | 1023 | 1024 | def button_callback(): 1025 | 1026 | theme_t = theme_select.value 1027 | if(theme_t == 'black'): 1028 | curdoc().theme= theme_black 1029 | elif(theme_t == 'gray'): 1030 | curdoc().theme= theme_gray 1031 | elif(theme_t == 'dark blue'): 1032 | curdoc().theme= theme_blue 1033 | 1034 | jk = create_figure(stack_montage_flag) 1035 | layout.children[1] = jk[0] 1036 | 1037 | print('############') 1038 | print('In Button callback') 1039 | tsne3 = jk[1] 1040 | print(type(tsne3)) 1041 | print(tsne3) 1042 | 1043 | 1044 | file_name_hover = jk[2] 1045 | print('############fnh in button callback') 1046 | print('file_name_hover') 1047 | print(file_name_hover) 1048 | 1049 | 1050 | 1051 | markers_single = jk[3] 1052 | print('############') 1053 | print(type(markers_single)) 1054 | print(markers_single) 1055 | 1056 | cluster_ms_list = jk[4] 1057 | print('############') 1058 | print(type(cluster_ms_list )) 1059 | print(cluster_ms_list ) 1060 | 1061 | p1_out = draw_tSNE_scatter(tsne3, file_name_hover, cluster_ms_list) 1062 | p1 = p1_out[0] 1063 | p2 = p1_out[1] 1064 | p3 = p1_out[2] 1065 | p4 = p1_out[3] 1066 | 1067 | source = p1_out[4] 1068 | 1069 | #panel to tabpanel 2024 1070 | tab1 = TabPanel(child=p1, title="Response") 1071 | tab2 = TabPanel(child=p2, title="Treatment") 1072 | tab3 = TabPanel(child=p3, title="Cluster Annotations") 1073 | tab4 = TabPanel(child=p4, title="Patient id") 1074 | 1075 | tabs = Tabs(tabs=[ tab1, tab2, tab3, tab4 ]) 1076 | layout.children[2] = tabs 1077 | 1078 | return([p,p1,p2,p3,p4,source,tabs]) 1079 | 1080 | 1081 | ############################################################################ 1082 | ### create_figure() collects the user choices from the GUI and #### 1083 | ### calls either the generate_stack_montage() for reading in #### 1084 | ### a single image or the generate_image_tSNE() for multiplexed images #### 1085 | ############################################################################ 1086 | 1087 | 1088 | 1089 | def create_figure(stack_montage_flag): 1090 | 1091 | p = figure(tools=TOOLS,x_range=x_range, y_range=y_range,width=1000,height=1000) 1092 | 1093 | rb_imtech = radio_button_group_imtech.value 1094 | 1095 | rb = radio_button_group.value 1096 | 1097 | rb_rs = radio_button_group_RS.value 1098 | 1099 | rb_shf = radio_button_group_Shf.value 1100 | 1101 | chk_box_marker = checkbox_group.active 1102 | 1103 | print(chk_box_marker) 1104 | 1105 | chk_box_marker_sm = checkbox_group_sm.active 1106 | print('**chk_box_marker_sm**') 1107 | print(chk_box_marker_sm) 1108 | 1109 | 1110 | if(rb=='No'): 1111 | rb_val = 0 1112 | elif(rb=='Based on Response'): 1113 | rb_val = 1 1114 | elif(rb=='Based on Treatment'): 1115 | rb_val = 2 1116 | elif(rb=='Based on Clusters'): 1117 | rb_val = 3 1118 | elif(rb=='Based on Patient id'): 1119 | rb_val = 4 1120 | 1121 | 1122 | 1123 | if(rb_rs=='Generate random co-ordinates'): 1124 | rb_rs_val = 1 1125 | elif(rb_rs=='Use t-SNE co-ordinates'): 1126 | rb_rs_val = 0 1127 | elif(rb_rs == 'Arrange in rows'): 1128 | rb_rs_val = 2 1129 | 1130 | 1131 | if(rb_shf=='Yes'): 1132 | rb_shf_val = 1 1133 | else: 1134 | rb_shf_val = 0 1135 | 1136 | 1137 | 1138 | if (rb_imtech == 'Vectra'): 1139 | rb_imtech_val = 0 1140 | elif (rb_imtech=='t-CyCIF'): 1141 | rb_imtech_val = 1 1142 | elif (rb_imtech=='CODEX'): 1143 | rb_imtech_val = 2 1144 | elif (rb_imtech=='CyCIF'): 1145 | rb_imtech_val = 3 1146 | 1147 | ## adding in the codex, vectra, tcycif, cycif options 1148 | # collecting the marker names for tcycif/codex stack montage 1149 | 1150 | cluster_ms_list = ['nil'] * len(LABELS_MARKERS)#[] 1151 | clust_ms_list = [] 1152 | clust_ms_list_1 = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/markers.csv'), 1153 | header= 0,index_col=None) 1154 | markers_single = np.array(clust_ms_list_1.iloc[:,2]).flatten() 1155 | 1156 | 1157 | ## need to update the markers_single array based on the order the files are read in 1158 | if (stack_montage_flag==True): 1159 | if (rb_imtech_val ==1): #SM for t-CyCIF data 1160 | print('in SM for t-CyCIF') 1161 | ms_list=[] 1162 | cluster_ms_list = [] 1163 | for i in range(markers_single.shape[0]): 1164 | print(i) 1165 | print(pat_fov_list[i]) 1166 | print(markers_single) 1167 | channel_num = np.int64(pat_fov_list[i].split("_40X_")[1].split(".tiff")[0]) #2024 np.int64 1168 | ms_list.append(markers_single[channel_num-1]) 1169 | 1170 | 1171 | markers_single = np.copy(np.array(ms_list)) 1172 | 1173 | 1174 | 1175 | # use this updated markers_single name order going forward 1176 | for i in range(markers_single.shape[0]): 1177 | 1178 | cluster_ms_list.append('Channel '+ markers_single[i]) 1179 | 1180 | elif (rb_imtech_val ==2): #SM for CODEX 1181 | ## for codex sm for tonsils 1182 | print('in SM for CODEX') 1183 | ## need to update the markers_single array based on the order the files are read in 1184 | ms_list=[] 1185 | cluster_ms_list = [] 1186 | for i in range(markers_single.shape[0]): 1187 | print(i) 1188 | print(pat_fov_list[i]) 1189 | 1190 | channel_name = pat_fov_list[i].split('.tif')[0].split('_')[3] 1191 | channel_num = np.int64(np.array(np.where(channel_name==markers_single)).flatten()) #2024 np.int64 1192 | ms_list.append(markers_single[channel_num]) 1193 | 1194 | markers_single = np.copy(np.array(ms_list)) 1195 | 1196 | 1197 | # use this updated markers_single name order going forward 1198 | for i in range(markers_single.shape[0]): 1199 | 1200 | cluster_ms_list.append('Channel '+ markers_single[i]) 1201 | 1202 | 1203 | elif (stack_montage_flag == False): 1204 | 1205 | 1206 | # to handle all markers in t-CyCIF/CODEX/Vectra 1207 | 1208 | mc = [] 1209 | for m in range(len(LABELS_MARKERS)): 1210 | in_mc = np.int64(np.array(np.where(LABELS_MARKERS[m] == np.array(markers_single))).flatten()) #2024 np.int64 1211 | mc.append(in_mc) 1212 | 1213 | if (rb_imtech_val ==0):#Vectra 1214 | wc = [100] * len(LABELS_MARKERS) 1215 | wc[0] = 800 1216 | wc[1]=wc[2] = 400 1217 | elif (rb_imtech_val ==1):#t-CyCIF 1218 | wc = [100] * len(LABELS_MARKERS) 1219 | wc[0] = 50 1220 | elif (rb_imtech_val ==2): 1221 | wc = [150] * len(LABELS_MARKERS) 1222 | wc[0] = 300 # 1223 | wc[6] = 100 1224 | 1225 | elif (rb_imtech_val ==3): 1226 | wc = [150] * len(LABELS_MARKERS) 1227 | wc[1] = 100 1228 | wc[2] = 300 1229 | 1230 | cluster_ms_list = ['nil'] * num_images #[] 1231 | 1232 | 1233 | 1234 | 1235 | ########### 1236 | 1237 | 1238 | if(len(chk_box_marker_sm)==0): #so we are using the multiple multiplexed option 1239 | if(len(chk_box_marker)==0): #if no markers are chosen, display defaults 1240 | 1241 | file_name_1 = "image_tSNE_GUI/static/image_tsne_tils_all.png" 1242 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1243 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 1244 | df_Xtsne.shape 1245 | tsne_points = np.array(df_Xtsne ) 1246 | file_name_hover = list(range(num_images)) 1247 | 1248 | else: 1249 | 1250 | 1251 | out1 = generate_image_tSNE(chk_box_marker, rb_val,rb_rs_val,rb_shf_val,rb_imtech_val, mc, wc, LABELS_MARKERS) 1252 | file_name_all = out1[0] 1253 | tsne_points = out1[1] 1254 | file_name_hover = out1[2] 1255 | 1256 | print('file_name_hover after gen_image_tsne') 1257 | print(file_name_hover) 1258 | 1259 | print('#########file_name_all########') 1260 | print('#################') 1261 | 1262 | print(file_name_all) 1263 | 1264 | file_name_1 = file_name_all.split('/code')[1] 1265 | print('#########file_name_1########') 1266 | print(file_name_1) 1267 | 1268 | 1269 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1270 | 1271 | elif(len(chk_box_marker_sm)==1): #stack montage option 1272 | print('chosen stack montage') 1273 | out1sm = generate_stack_montage(chk_box_marker, rb_imtech_val, LABELS_MARKERS) 1274 | file_name_all = out1sm[0] 1275 | tsne_points = out1sm[1] 1276 | file_name_hover = out1sm[2] 1277 | 1278 | print('############fnh post sm') 1279 | print('file_name_hover') 1280 | print(file_name_hover) 1281 | 1282 | print('#########file_name_all########') 1283 | print('#################') 1284 | 1285 | print(file_name_all) 1286 | 1287 | file_name_1 = file_name_all.split('/code')[1] 1288 | print('#########file_name_1########') 1289 | print(file_name_1) 1290 | 1291 | 1292 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1293 | 1294 | 1295 | return ([p,tsne_points, file_name_hover,markers_single, cluster_ms_list]) 1296 | 1297 | 1298 | ########## 1299 | # define the colour vectors 1300 | colours_58 = ["firebrick","gold","royalblue","green","dimgray","orchid","darkviolet", 1301 | "red", "orange", "limegreen", "blue", "purple", "seagreen","gold","darkolivegreen", 1302 | "lightpink","thistle","mistyrose","saddlebrown","slategrey","powderblue", 1303 | "palevioletred","mediumvioletred","yellowgreen","lemonchiffon","chocolate", 1304 | "lightsalmon","lightcyan","lightblue", "darkorange","black","darkblue","darkgreen","paleturquoise","yellow","rosybrown", 1305 | "steelblue","dodgerblue","darkkhaki","lime","coral","aquamarine","mediumpurple","violet","plum", 1306 | "deeppink","navy","seagreen","teal","mediumspringgreen","cadetblue", 1307 | "maroon","silver","sienna","crimson","slateblue","magenta","darkmagenta"] 1308 | 1309 | colours_resp = ["yellow","red","green"] 1310 | colours_tx = ["orange","limegreen","violet"] 1311 | 1312 | 1313 | ##################################### 1314 | #### Section that gets populated #### 1315 | ####based on User uploads ########### 1316 | ##################################### 1317 | ##################################### 1318 | ## This section reads in images, #### 1319 | ## metadata, markers in the data,#### 1320 | ## and user choices #### 1321 | ##################################### 1322 | ##################################### 1323 | 1324 | 1325 | path_wd = os.getcwd() 1326 | print('Current working directory: ') 1327 | print(path_wd) 1328 | 1329 | 1330 | 1331 | 1332 | ### to generate tsne points from the onset itself 1333 | 1334 | fname = os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv') 1335 | if os.path.isfile(fname): 1336 | df_Xtsne = pd.read_csv(fname, index_col=None, header= None) 1337 | tsne = np.array(df_Xtsne ) 1338 | tx, ty = tsne[:,0], tsne[:,1] 1339 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 1340 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 1341 | num_images = tsne.shape[0] 1342 | 1343 | 1344 | 1345 | ## to have the arrange_in_rows and random points already available 1346 | # Choose up to k points around each reference point as candidates for a new 1347 | # sample point 1348 | k = 10 1349 | 1350 | # Minimum distance between samples 1351 | r = 1.7 1352 | 1353 | width_1, height_1 = 20, 22 1354 | 1355 | print('Generating random co-ordinates') 1356 | 1357 | # Cell side length 1358 | a = r/np.sqrt(2) 1359 | # Number of cells in the x- and y-directions of the grid 1360 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1361 | 1362 | # A list of coordinates in the grid of cells 1363 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1364 | # Initilalize the dictionary of cells: each key is a cell's coordinates, the 1365 | # corresponding value is the index of that cell's point's coordinates in the 1366 | # samples list (or None if the cell is empty). 1367 | cells = {coords: None for coords in coords_list} 1368 | 1369 | 1370 | 1371 | # Pick a random point to start with. 1372 | pt = (np.random.uniform(0, width_1), np.random.uniform(0, height_1)) 1373 | samples = [pt] 1374 | # Our first sample is indexed at 0 in the samples list... 1375 | cells[get_cell_coords(pt,a)] = 0 1376 | # ... and it is active, in the sense that we're going to look for more points 1377 | # in its neighbourhood. 1378 | active = [0] 1379 | 1380 | nsamples = 1 1381 | # As long as there are points in the active list, keep trying to find samples. 1382 | while (nsamples < num_images): #active: 1383 | # choose a random "reference" point from the active list. 1384 | idx = np.random.choice(active) 1385 | refpt = samples[idx] 1386 | # Try to pick a new point relative to the reference point. 1387 | pt = get_point(k, refpt,r,a,nx,ny,cells,samples) 1388 | if pt: 1389 | # Point pt is valid: add it to the samples list and mark it as active 1390 | samples.append(pt) 1391 | nsamples += 1 1392 | active.append(len(samples)-1) 1393 | cells[get_cell_coords(pt,a)] = len(samples) - 1 1394 | print('nsamples is: ',str(nsamples)) 1395 | else: 1396 | # We had to give up looking for valid points near refpt, so remove it 1397 | # from the list of "active" points. 1398 | active.remove(idx) 1399 | 1400 | tsne = np.asarray(samples) 1401 | tx, ty = tsne[:,0], tsne[:,1] 1402 | 1403 | tx[tx ==0] = 0.2 1404 | ty[ty ==0] = 0.1 1405 | 1406 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 1407 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 1408 | 1409 | df_Xtsne_m = pd.DataFrame(tsne) 1410 | #df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), header=None, index=None) 1411 | df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), header=None, index=None) #14th march 2022 1412 | 1413 | 1414 | ##### for 'arrange in rows' option at the onset itself 1415 | 1416 | 1417 | # Minimum distance between samples 1418 | r = 2 1419 | 1420 | width_1, height_1 = 20, 22 1421 | 1422 | print('Arrange images side-by-side') 1423 | 1424 | # Cell side length 1425 | a = r/np.sqrt(2) 1426 | # Number of cells in the x- and y-directions of the grid 1427 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1428 | 1429 | # A list of coordinates in the grid of cells 1430 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1431 | 1432 | 1433 | #logic to get grid points - 29th March 2021 1434 | m = np.int64(np.floor(nx*ny/num_images)) #2024 np.int to int64 1435 | 1436 | 1437 | row_needed = [] 1438 | def multiples(m, num_images): 1439 | for i in range(num_images): 1440 | row_needed.append(i*m) 1441 | 1442 | multiples(m,num_images) 1443 | 1444 | 1445 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 1446 | 1447 | print(type(select_coords)) 1448 | 1449 | tsne1 = select_coords 1450 | 1451 | tsne1[tsne1 ==0] = 0.2 1452 | 1453 | df_Xtsne = pd.DataFrame(tsne1) 1454 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), header=None, index=None) 1455 | 1456 | 1457 | else: 1458 | #read in images 1459 | FoV_path = os.path.join(path_wd + '/user_inputs/figures/') 1460 | count_images = 0 1461 | for fname in os.listdir(FoV_path): 1462 | print(fname) 1463 | count_images = count_images + 1 1464 | num_images = count_images 1465 | #generate random 2D coords for these images 1466 | 1467 | #### 1468 | #### tsne and clustering within mistic, if not provided by user 1469 | #### 1470 | 1471 | im1 = tifffile.imread(os.path.join(FoV_path+fname)) 1472 | 1473 | if (im1.shape[0] < 6): 1474 | num_markers = im1.shape[0] 1475 | else: 1476 | num_markers = 6 1477 | 1478 | data_CM = np.zeros((1,num_markers)) # one row = one image with mean of markers in the columns, defaults to 6 markers 1479 | 1480 | for fname in os.listdir(FoV_path): 1481 | print(fname) 1482 | im1 = tifffile.imread(os.path.join(FoV_path+fname)) 1483 | if ((im1.shape[0]==16) and (im1.shape[1]==4) and (im1.shape[2]==5040) and (im1.shape[3]==9408)): #for codex intake 1484 | im1 = im1.reshape(64,5040,9408) 1485 | 1486 | #if (rb_imtech_val ==2): #for codex 1487 | # im = im.reshape(64,5040,9408) 1488 | 1489 | 1490 | 1491 | image_summary_vec = np.zeros((1,num_markers)) 1492 | for m in range(num_markers): 1493 | image1 = im1[m] 1494 | 1495 | median_filtered1 = scipy.ndimage.median_filter(image1, size=1) 1496 | 1497 | 1498 | thresh = threshold_otsu(median_filtered1) 1499 | 1500 | 1501 | bw = closing(image1 > thresh, square(1)) 1502 | 1503 | # remove artifacts connected to image border 1504 | cleared_imtsne = clear_border(bw) 1505 | 1506 | 1507 | image_summary_vec[0,m] = np.mean(cleared_imtsne) #mean of each channel m per image fname 1508 | 1509 | 1510 | 1511 | data_CM = np.concatenate((data_CM,image_summary_vec),axis=0) 1512 | 1513 | data_CM = np.delete(data_CM, (0), axis=0) 1514 | 1515 | # perform tSNE 1516 | tsne = TSNE(n_components=2, verbose=1, perplexity=5)#, n_iter=900) #2024, setting perplexity value to be thresh)#2024, square(1)) 1802 | 1803 | # remove artifacts connected to image border 1804 | cleared_imtsne = clear_border(bw) 1805 | 1806 | 1807 | image_summary_vec[0,m] = np.mean(cleared_imtsne) #mean of each channel m per image fname 1808 | 1809 | 1810 | 1811 | data_CM = np.concatenate((data_CM,image_summary_vec),axis=0) 1812 | 1813 | data_CM = np.delete(data_CM, (0), axis=0) 1814 | 1815 | 1816 | 1817 | # Fit a Dirichlet process mixture of Gaussians using n components 1818 | bgm = BayesianGaussianMixture(n_components=3, random_state=42).fit(data_CM) 1819 | cluster_asgn = bgm.predict(data_CM) 1820 | 1821 | df_dpgmm_u = pd.DataFrame(cluster_asgn) 1822 | df_dpgmm_u.to_csv(os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv'), header=None, index=None) 1823 | 1824 | 1825 | fname2 = os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv') 1826 | 1827 | 1828 | clust_asgn_list_2 = np.array(pd.read_csv(fname2, header= None,index_col=None)).flatten() 1829 | for i in range(num_images): 1830 | 1831 | color_vec_clasgn.append(colours_58[clust_asgn_list_2[i]]) 1832 | clust_asgn_list.append(clust_asgn_list_2[i]) 1833 | cluster_anno_list.append('Cluster '+ str(clust_asgn_list_2[i])) 1834 | 1835 | ## bayes clustering end 1836 | 1837 | 1838 | 1839 | # collecting the patient id metadata 1840 | 1841 | fname = os.path.join(path_wd + '/user_inputs/metadata/Patient_ids.csv') 1842 | color_vec_patid = [] 1843 | cluster_pat_list = [] 1844 | clust_patid_list = [] 1845 | 1846 | if os.path.isfile(fname): 1847 | 1848 | 1849 | # collecting the Patient id metadata 1850 | 1851 | clust_patid_list_1 = np.array(pd.read_csv(fname,header= None,index_col=None)).flatten() 1852 | pat_ind_list = np.copy(clust_patid_list_1) 1853 | 1854 | for i in range(clust_patid_list_1.shape[0]): 1855 | 1856 | color_vec_patid.append(colours_58[clust_patid_list_1[i]]) 1857 | clust_patid_list.append(clust_patid_list_1[i]) 1858 | cluster_pat_list.append('Patient '+ str(clust_patid_list_1[i])) 1859 | 1860 | else: 1861 | pat_ind_list = [] 1862 | for i in range(num_images): 1863 | color_vec_patid.append('gray') 1864 | clust_patid_list.append('nil') 1865 | cluster_pat_list.append('Patient nil') 1866 | pat_ind_list.append('nil') 1867 | 1868 | # generate dummy thumbnail names for hover panel 1869 | 1870 | file_name_hover = [] 1871 | file_name_hover_list = [] 1872 | 1873 | for i in range(num_images): #clust_patid_list_1.shape[0]): 1874 | 1875 | file_name_hover.append(str(i)) 1876 | file_name_hover_list.append('Thumbnail '+ str(i)) 1877 | 1878 | 1879 | 1880 | 1881 | 1882 | ################# 1883 | # set up widgets 1884 | ################# 1885 | 1886 | RB_1 = ['Based on Response', 'Based on Treatment','Based on Clusters','Based on Patient id','No'] 1887 | 1888 | RB_2 = ['Generate random co-ordinates', 'Use t-SNE co-ordinates', 'Arrange in rows'] 1889 | 1890 | RB_3 = ['Yes', 'No'] 1891 | 1892 | RB_4 = ['Vectra', 't-CyCIF', 'CODEX', 'CyCIF'] 1893 | 1894 | TS_1 = ['black','gray', 'dark blue'] 1895 | 1896 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,save,box_select," 1897 | 1898 | x_range=(0,1) 1899 | y_range=(0,1) 1900 | 1901 | 1902 | #main image tSNE canvas 1903 | p = figure(tools=TOOLS,x_range=x_range, y_range=y_range,width=1000,height=1000) 1904 | tsne_points = np.zeros([1,2]) 1905 | 1906 | #additional tSNE scatter plot canvas 1907 | 1908 | 1909 | #2024 plot_width to width, same for height 1910 | point_tSNE = figure(width=350, height=350, 1911 | tools='hover,pan,wheel_zoom,box_select,reset') 1912 | point_tSNE.title.text = 'tSNE point cloud for Patient response' 1913 | 1914 | 1915 | point_tSNE.scatter(tsne[:,0],tsne[:,1],fill_alpha=0.6, color ='red',size=8,legend_label='Response') #2024 legend to legend_label 1916 | 1917 | point_tSNE.legend.location = "bottom_left" 1918 | 1919 | #2024 Figure to figure 1920 | theme_black = Theme(json={ 1921 | 'attrs': { 1922 | 'figure': { 1923 | 'background_fill_color': '#2F2F2F', 1924 | 'border_fill_color': '#2F2F2F', 1925 | 'outline_line_color': '#444444' 1926 | }, 1927 | 'Axis': { 1928 | 'axis_line_color': "white", 1929 | 'axis_label_text_color': "white", 1930 | 'major_label_text_color': "white", 1931 | 'major_tick_line_color': "white", 1932 | 'minor_tick_line_color': "white", 1933 | 'minor_tick_line_color': "white" 1934 | }, 1935 | 'Grid': { 1936 | 'grid_line_dash': [6, 4], 1937 | 'grid_line_alpha': .3 1938 | }, 1939 | 'Circle': { 1940 | 'fill_color': 'lightblue', 1941 | 'size': 10, 1942 | }, 1943 | 'Title': { 1944 | 'text_color': "white" 1945 | } 1946 | } 1947 | }) 1948 | 1949 | theme_gray = Theme(json={ 1950 | 'attrs': { 1951 | 'figure': { 1952 | 'background_fill_color': '#555555', 1953 | 'border_fill_color': '#2F2F2F', 1954 | 'outline_line_color': '#444444' 1955 | }, 1956 | 'Axis': { 1957 | 'axis_line_color': "white", 1958 | 'axis_label_text_color': "white", 1959 | 'major_label_text_color': "white", 1960 | 'major_tick_line_color': "white", 1961 | 'minor_tick_line_color': "white", 1962 | 'minor_tick_line_color': "white" 1963 | }, 1964 | 'Grid': { 1965 | 'grid_line_dash': [6, 4], 1966 | 'grid_line_alpha': .3 1967 | }, 1968 | 'Circle': { 1969 | 'fill_color': 'lightblue', 1970 | 'size': 10, 1971 | }, 1972 | 'Title': { 1973 | 'text_color': "white" 1974 | } 1975 | } 1976 | }) 1977 | 1978 | theme_blue = Theme(json={ 1979 | 'attrs': { 1980 | 'figure': { 1981 | 'background_fill_color': '#25256d', 1982 | 'border_fill_color': '#2F2F2F', 1983 | 'outline_line_color': '#444444' 1984 | }, 1985 | 'Axis': { 1986 | 'axis_line_color': "white", 1987 | 'axis_label_text_color': "white", 1988 | 'major_label_text_color': "white", 1989 | 'major_tick_line_color': "white", 1990 | 'minor_tick_line_color': "white", 1991 | 'minor_tick_line_color': "white" 1992 | }, 1993 | 'Grid': { 1994 | 'grid_line_dash': [6, 4], 1995 | 'grid_line_alpha': .3 1996 | }, 1997 | 'Circle': { 1998 | 'fill_color': 'lightblue', 1999 | 'size': 10, 2000 | }, 2001 | 'Title': { 2002 | 'text_color': "white" 2003 | } 2004 | } 2005 | }) 2006 | 2007 | ######### 2008 | ######### 2009 | 2010 | 2011 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select," 2012 | 2013 | 2014 | TOOLTIPS = [ 2015 | ("index", "$index"), 2016 | ("(x,y)", "($x, $y)"), 2017 | ("Pat_id", "@pat_list"), 2018 | ("Response", "@res_list"), 2019 | ("Treatment", "@tx_list"), 2020 | ("Cluster id", "@clust_asgn_list"), 2021 | ("Channel", "@cluster_ms_list"), 2022 | ("Thumbnail", "@file_name_hover_list"), 2023 | ("FoV","@fov_list") 2024 | ] 2025 | 2026 | p1 = figure(width=400, height=400, tooltips=TOOLTIPS,tools = TOOLS, 2027 | title="Patient response") #2024 width and height 2028 | 2029 | 2030 | 2031 | 2032 | ##################################################################################### 2033 | ## This block prepares and sets up the GUI layout, and collects user-input choices ## 2034 | ##################################################################################### 2035 | 2036 | 2037 | desc = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/desc.html')).read(), sizing_mode="stretch_width") 2038 | 2039 | 2040 | desc_SM1 = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/descSM1.html')).read(), sizing_mode="stretch_width") 2041 | 2042 | desc_SM = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/descMontage.html')).read(), sizing_mode="stretch_width") 2043 | 2044 | 2045 | 2046 | radio_button_group_imtech = Select(value='Vectra', 2047 | title='', 2048 | width=200, 2049 | options=RB_4) 2050 | 2051 | 2052 | radio_button_group = Select(value='No', 2053 | title='Image border', 2054 | width=200, 2055 | options=RB_1) 2056 | 2057 | radio_button_group_RS = Select(value='Generate new co-ordinates', 2058 | title='tSNE co-ordinates', 2059 | width=220, 2060 | options=RB_2) 2061 | 2062 | radio_button_group_Shf = Select(value='Yes', 2063 | title='Shuffle images', 2064 | width=200, 2065 | options=RB_3) 2066 | 2067 | theme_select = Select(value = 'black', 2068 | title='Theme', 2069 | width = 200, 2070 | options = TS_1) 2071 | 2072 | 2073 | checkbox_group = CheckboxGroup(labels=LABELS_MARKERS)#, active=[0, 1]) 2074 | 2075 | 2076 | button = Button(label='Run', width=100, button_type="success") 2077 | 2078 | ## added to activate Run button 2079 | button.on_click(button_callback) 2080 | 2081 | ## for stack montage 2082 | SM = ['Stack montage'] 2083 | checkbox_group_sm = CheckboxGroup(labels=SM) 2084 | 2085 | 2086 | print('updating new p1#') 2087 | 2088 | 2089 | 2090 | # set up layout and GUI refresh after every 'Run' click 2091 | 2092 | out2 = create_figure(stack_montage_flag) 2093 | print(out2) 2094 | p = out2[0] 2095 | tsne2 = out2[1] 2096 | print('############') 2097 | print(type(tsne2)) 2098 | print(tsne2) 2099 | 2100 | file_name_hover = out2[2] 2101 | print('############fnh after create figure') 2102 | print(type(file_name_hover)) 2103 | print(file_name_hover) 2104 | 2105 | 2106 | markers_single = out2[3] 2107 | print('############') 2108 | print(type(markers_single)) 2109 | print(markers_single) 2110 | 2111 | cluster_ms_list = out2[4] 2112 | print('############') 2113 | print(type(cluster_ms_list )) 2114 | print(cluster_ms_list ) 2115 | 2116 | 2117 | p11_out = draw_tSNE_scatter(tsne2, file_name_hover,cluster_ms_list ) 2118 | 2119 | p1 = p11_out[0] 2120 | p2 = p11_out[1] 2121 | p3 = p11_out[2] 2122 | p4 = p11_out[3] 2123 | 2124 | #panel to tabpanel 2024 2125 | tab1 = TabPanel(child=p1, title="Response") 2126 | tab2 = TabPanel(child=p2, title="Treatment") 2127 | tab3 = TabPanel(child=p3, title="Cluster Annotations") 2128 | tab4 = TabPanel(child=p4, title="Patient id") 2129 | 2130 | tabs = Tabs(tabs=[ tab1, tab2, tab3, tab4 ]) # (added tab4) 2131 | 2132 | 2133 | 2134 | selects = column(desc, radio_button_group_imtech, desc_SM1, checkbox_group_sm, desc_SM, checkbox_group, radio_button_group, radio_button_group_RS, radio_button_group_Shf, theme_select, button, width=520) # 2135 | 2136 | layout=row(selects,p, tabs)#,selected_points)#create_figure()) 2137 | 2138 | #doc = curdoc() 2139 | curdoc().theme= theme_black 2140 | 2141 | 2142 | # add to document 2143 | curdoc().add_root(layout) 2144 | 2145 | curdoc().title = "Mistic: Image tSNE viewer" 2146 | 2147 | 2148 | 2149 | # cd image_tSNE_code/bokeh_GUI/bokeh-branch-2.3/examples/app 2150 | # bokeh serve --port 5098 --show image_tSNE_GUI 2151 | 2152 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/main_old.py: -------------------------------------------------------------------------------- 1 | ### Code for Mistic 2 | ### Image t-SNE viewer for multiplexed images 3 | ### 4 | ### code author Sandhya Prabhakaran 5 | ### 6 | ### Revision history 7 | ### FoV code ### 8 | ### Jan 29th 2021 9 | ### 25th June 2021 10 | ### github version 11 | ### 19th Jan 2022 12 | ### added Patient id live canvas 13 | ### 26th Jan 2022 ### 14 | ### merging the stack montage code 15 | ### prior version is main_rollback. 16 | ### 14th Feb 2022 17 | ### adding codex, tcycif, vectra option 18 | ### prior version is main_rollback_1 19 | ### 14th mar 2022 20 | ### added the user tsne as well 'arrange in rows' (seeall) tsne generation code 21 | 22 | ## 20th Feb 2022 23 | ## this is the latest main.py, with dates and comments removed 24 | ## for github upload 25 | 26 | ## 8th April 2022 27 | ## 2nd rebuttal updates 28 | ## tsne and dpmm code added 29 | 30 | #### 21st April 2022 31 | #### CyCIF files 32 | ## code cleanup for github 33 | 34 | 35 | import os 36 | import sys 37 | import random 38 | import warnings 39 | 40 | import numpy as np 41 | import pandas as pd 42 | 43 | import matplotlib 44 | import matplotlib.pyplot as plt 45 | import matplotlib.patches as mpatches 46 | 47 | from matplotlib.figure import Figure 48 | 49 | from matplotlib import cm 50 | 51 | from scipy import ndimage 52 | 53 | from sklearn.manifold import TSNE 54 | 55 | import phenograph as pg 56 | from scipy.stats import zscore 57 | 58 | from skimage.color import rgb2gray 59 | 60 | from matplotlib.patches import Polygon 61 | 62 | from skimage import io 63 | 64 | from skimage import data 65 | from skimage.filters import threshold_otsu 66 | from skimage.segmentation import clear_border 67 | from skimage.measure import label, regionprops 68 | from skimage.morphology import closing, square 69 | from skimage.color import label2rgb 70 | from skimage.io import imread, imshow, imread_collection, concatenate_images 71 | from skimage.transform import resize 72 | from skimage.morphology import label 73 | 74 | import seaborn as sns 75 | 76 | import scipy.spatial 77 | 78 | from scipy.spatial import distance 79 | 80 | 81 | import seaborn as sns 82 | 83 | 84 | import matplotlib.image as mpimg 85 | 86 | 87 | import matplotlib.colors as colors 88 | 89 | from matplotlib import colors as mcolors 90 | 91 | colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS) 92 | 93 | from PIL import Image, ImageOps 94 | 95 | from bokeh.layouts import column, row 96 | from bokeh.models import (Select, Button) 97 | from bokeh.palettes import Spectral5 98 | from bokeh.plotting import curdoc, figure 99 | from bokeh.models.widgets import Div 100 | from bokeh.layouts import column, layout 101 | 102 | from bokeh.models import (HoverTool, ColumnDataSource) 103 | from bokeh.models.widgets import RadioButtonGroup 104 | from bokeh.models import CheckboxGroup 105 | from bokeh.models import CustomJS 106 | from bokeh.models import BoxSelectTool 107 | from bokeh.themes import Theme 108 | 109 | from bokeh.models.widgets import Panel, Tabs 110 | from bokeh.io import output_file, show 111 | 112 | 113 | ## 114 | import skimage.io as io 115 | import tifffile 116 | from PIL import TiffImagePlugin 117 | 118 | from sklearn.mixture import BayesianGaussianMixture 119 | 120 | 121 | 122 | ### image-tSNE 123 | 124 | width = 5000 125 | height = 5000 126 | max_dim = 500 127 | tw =1200 128 | th = 900 129 | 130 | full_image_1 = Image.new('RGBA', (width, height)) 131 | 132 | 133 | 134 | ############################################################## 135 | 136 | ################## Function definitions ###################### 137 | 138 | ############################################################## 139 | 140 | 141 | ####################################################################### 142 | ##### get_cell_coords(), get_neighbours(), point_valid(), #### 143 | ##### get_point() are functions to generate random points #### 144 | ##### functions modified from: #### 145 | ##### https://scipython.com/blog/poisson-disc-sampling-in-python/ #### 146 | ####################################################################### 147 | 148 | 149 | 150 | def get_cell_coords(pt,a): 151 | """Get the coordinates of the cell that pt = (x,y) falls in.""" 152 | 153 | return int(pt[0] // a), int(pt[1] // a) 154 | 155 | def get_neighbours(coords,nx,ny,cells): 156 | """Return the indexes of points in cells neighbouring cell at coords. 157 | For the cell at coords = (x,y), return the indexes of points in the cells 158 | with neighbouring coordinates illustrated below: ie those cells that could 159 | contain points closer than r. 160 | ooo 161 | ooooo 162 | ooXoo 163 | ooooo 164 | ooo 165 | """ 166 | 167 | dxdy = [(-1,-2),(0,-2),(1,-2),(-2,-1),(-1,-1),(0,-1),(1,-1),(2,-1), 168 | (-2,0),(-1,0),(1,0),(2,0),(-2,1),(-1,1),(0,1),(1,1),(2,1), 169 | (-1,2),(0,2),(1,2),(0,0)] 170 | neighbours = [] 171 | for dx, dy in dxdy: 172 | neighbour_coords = coords[0] + dx, coords[1] + dy 173 | if not (0 <= neighbour_coords[0] < nx and 174 | 0 <= neighbour_coords[1] < ny): 175 | # We're off the grid: no neighbours here. 176 | continue 177 | neighbour_cell = cells[neighbour_coords] 178 | if neighbour_cell is not None: 179 | # This cell is occupied: store this index of the contained point. 180 | neighbours.append(neighbour_cell) 181 | return neighbours 182 | 183 | def point_valid(pt,a,nx,ny,cells,samples,r): 184 | """Is pt a valid point to emit as a sample? 185 | It must be no closer than r from any other point: check the cells in its 186 | immediate neighbourhood. 187 | """ 188 | 189 | cell_coords = get_cell_coords(pt,a) 190 | for idx in get_neighbours(cell_coords,nx,ny,cells): 191 | nearby_pt = samples[idx] 192 | # Squared distance between or candidate point, pt, and this nearby_pt. 193 | distance2 = (nearby_pt[0]-pt[0])**2 + (nearby_pt[1]-pt[1])**2 194 | if distance2 < r**2: 195 | # The points are too close, so pt is not a candidate. 196 | return False 197 | # All points tested: if we're here, pt is valid 198 | return True 199 | 200 | def get_point(k, refpt,r,a,nx,ny,cells,samples): 201 | """Try to find a candidate point relative to refpt to emit in the sample. 202 | We draw up to k points from the annulus of inner radius r, outer radius 2r 203 | around the reference point, refpt. If none of them are suitable (because 204 | they're too close to existing points in the sample), return False. 205 | Otherwise, return the pt. 206 | """ 207 | i = 0 208 | while i < k: 209 | rho, theta = np.random.uniform(r, 2*r), np.random.uniform(0, 2*np.pi) 210 | pt = refpt[0] + rho*np.cos(theta), refpt[1] + rho*np.sin(theta) 211 | if not (0 <= pt[0] < width and 0 <= pt[1] < height): 212 | # This point falls outside the domain, so try again. 213 | continue 214 | if point_valid(pt,a,nx,ny,cells,samples,r): 215 | return pt 216 | i += 1 217 | # We failed to find a suitable point in the vicinity of refpt. 218 | return False 219 | 220 | ############################################################### 221 | ### draw_tSNE_scatter() #### 222 | ### a) creates the metadata-based tSNE scatter plots #### 223 | ### b) populates the hover functionality for each tSNE dot #### 224 | ############################################################### 225 | 226 | def draw_tSNE_scatter(tsne1, file_name_hover,cluster_ms_list ): 227 | tsne=np.asarray(tsne1) 228 | 229 | source = ColumnDataSource(data=dict( 230 | x=tsne[:,0], 231 | y=tsne[:,1], 232 | pat_list = pat_ind_list, 233 | res_list = resp_list, 234 | fov_list = pat_fov_list, 235 | color_vec_list = color_vec, 236 | tx_list = tx_list, 237 | color_vec_tx_list = color_vec_tx, 238 | clust_asgn_list = clust_asgn_list, 239 | color_vec_clasgn_list = color_vec_clasgn, 240 | cluster_anno_list = cluster_anno_list, 241 | cluster_ms_list = cluster_ms_list, 242 | cluster_pat_list = cluster_pat_list, 243 | color_vec_patid_list = color_vec_patid, 244 | file_name_hover_list = file_name_hover 245 | #legend_p11 = legend_p1 246 | )) 247 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select," 248 | 249 | TOOLTIPS = [ 250 | ("index", "$index"), 251 | ("(x,y)", "($x, $y)"), 252 | ("Pat_id", "@pat_list"), 253 | ("Response", "@res_list"), 254 | ("Treatment", "@tx_list"), 255 | ("Cluster id", "@clust_asgn_list"), 256 | ("Channel", "@cluster_ms_list"), 257 | ("Thumbnail", "@file_name_hover_list"), 258 | ("FoV","@fov_list") 259 | ] 260 | 261 | 262 | 263 | 264 | p1 = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,tools = TOOLS, 265 | title="Patient response") 266 | 267 | p1.scatter('x', 'y', size=10, source=source, legend= 'res_list', color = 'color_vec_list',fill_alpha=0.6) 268 | p1.legend.location = "bottom_left" 269 | 270 | 271 | p2 = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,tools = TOOLS, 272 | title="Treatment category") 273 | p2.scatter('x', 'y', size=10, source=source, legend= 'tx_list', color = 'color_vec_tx_list',fill_alpha=0.6) 274 | p2.legend.location = "bottom_left" 275 | 276 | 277 | p3 = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,tools = TOOLS, 278 | title="Cluster annotations") 279 | p3.scatter('x', 'y', size=10, source=source, legend= 'cluster_anno_list', color = 'color_vec_clasgn_list',fill_alpha=0.6) 280 | p3.legend.location = "bottom_left" 281 | 282 | 283 | 284 | p4 = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,tools = TOOLS, 285 | title="Patient id") 286 | p4.scatter('x', 'y', size=10, source=source, legend= 'cluster_pat_list', color = 'color_vec_patid_list',fill_alpha=0.6) 287 | p4.legend.location = "bottom_left" 288 | 289 | 290 | return ([p1,p2,p3,p4, source]) 291 | 292 | 293 | 294 | 295 | 296 | ############################################################################################## 297 | ### generate_stack_montage() #### 298 | ### a) reads in and processes the image channels #### 299 | ### b) generates evenly-spaced points on the static canvas to arrange the images in rows #### 300 | ### c) generates thumbnails, and pastes these onto the static canvas #### 301 | ### d) stores the thumbnails in the output folder #### 302 | ### e) updates the hover tool with thumbnail paths, marker names and metadata #### 303 | ############################################################################################## 304 | 305 | 306 | ################## 307 | def generate_stack_montage(chk_box_marker_sm, rb_imtech_val, LABELS_MARKERS): 308 | 309 | 310 | 311 | full_image = Image.new('RGBA', (width, height)) 312 | size = [256,256] 313 | 314 | file_name_hover = [] 315 | file_name_hover_list = [] 316 | 317 | # Choose up to k points around each reference point as candidates for a new 318 | # sample point 319 | k = 10 320 | 321 | # Minimum distance between samples 322 | r = 2 323 | 324 | width_1, height_1 = 20, 22 325 | 326 | print('Arrange images side-by-side') 327 | 328 | # Cell side length 329 | a = r/np.sqrt(2) 330 | # Number of cells in the x- and y-directions of the grid 331 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 332 | 333 | # A list of coordinates in the grid of cells 334 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 335 | 336 | 337 | #logic to get grid points 338 | m = np.int(np.floor(nx*ny/num_images)) 339 | 340 | 341 | row_needed = [] 342 | def multiples(m, num_images): 343 | for i in range(num_images): 344 | row_needed.append(i*m) 345 | 346 | multiples(m,num_images) 347 | 348 | 349 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 350 | 351 | print(type(select_coords)) 352 | ################ 353 | 354 | #tsne = np.asarray(coords_list) 355 | tsne = select_coords 356 | df_Xtsne = pd.DataFrame(tsne) 357 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_sm.csv'), header=None, index=None) 358 | 359 | 360 | 361 | # save the tSNE points to bve read later on irrespective of whoch option was chosen 362 | df_Xtsne_touse = pd.DataFrame(tsne) 363 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse_sm.csv'), header=None, index=None) 364 | 365 | tx, ty = tsne[:,0], tsne[:,1] 366 | 367 | tx[tx ==0] = 0.2 368 | ty[ty ==0] = 0.1 369 | 370 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 371 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 372 | print('###tx') 373 | print(tx) 374 | print(ty) 375 | inds = range(len(marker_image_list)) 376 | 377 | N = len(inds) 378 | 379 | 380 | for k in range(len(inds)): 381 | 382 | image_all_2 = [] 383 | image_all_1 = [] 384 | 385 | im = Image.open(marker_image_list[inds[k]]) 386 | 387 | 388 | for m in range(1): 389 | image = im 390 | 391 | median_filtered = scipy.ndimage.median_filter(image, size=1) 392 | image_all_2.append(median_filtered) 393 | 394 | 395 | # apply threshold 396 | 397 | thresh = threshold_otsu(image_all_2[m]) 398 | 399 | 400 | 401 | 402 | print('thresh is: ', str(thresh)) 403 | 404 | bw = closing(image > thresh, square(1)) 405 | 406 | # remove artifacts connected to image border 407 | cleared_imtsne = clear_border(bw) 408 | 409 | 410 | image_all_1.append(cleared_imtsne*100) 411 | 412 | tl = sum(image_all_1) 413 | 414 | if (rb_imtech_val ==0): #vectra 415 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) 416 | 417 | elif (rb_imtech_val ==1): #t-CyCIF 418 | tile_1 = Image.fromarray(np.uint8(cm.hsv(tl)*500)) 419 | elif (rb_imtech_val ==2): # CODEX 420 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 421 | elif (rb_imtech_val ==3): # CyCIF 422 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 423 | 424 | old_size = tile_1.size 425 | 426 | 427 | 428 | 429 | 430 | new_size = (old_size[0]+1, old_size[1]+1) 431 | new_im = Image.new("RGB", new_size,color='black') 432 | 433 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 434 | 435 | 436 | 437 | file_name = os.path.join(path_wd+'/output_tiles/sm_image_tsne_tils'+str(k)+'.png') 438 | 439 | file_name_hover.append('sm_image_tsne_tils'+str(k)+'.png') 440 | 441 | new_im.save(file_name) 442 | 443 | # Read Images 444 | 445 | 446 | tile_2 = Image.open(file_name) 447 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 448 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 449 | 450 | full_image.paste(tile_2, (int((width-max_dim)*ty[k]), int((height-max_dim)*tx[k])),mask=tile_2.convert('RGBA')) 451 | 452 | matplotlib.pyplot.figure(figsize = (25,20)) 453 | plt.imshow(full_image) 454 | plt.axis('off') 455 | 456 | 457 | 458 | 459 | 460 | k = random.randint(2,500) 461 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/sm_image_tsne_tils_all_'+str(k)+'.png') 462 | full_image.save(file_name) 463 | 464 | 465 | 466 | rotated_img = full_image.rotate(90) 467 | file_name_rot = os.path.join(path_wd+'/image_tSNE_GUI/static/sm_image_tsne_tils_all_'+str(k)+'_rot.png') 468 | 469 | 470 | rotated_img.save(file_name_rot) 471 | 472 | return([file_name_rot,tsne, file_name_hover]) 473 | 474 | 475 | 476 | ########################################################################################################### 477 | ### generate_image_tSNE() ######## 478 | ### ######## 479 | ### a) reads in and pre-processes the images ######## 480 | ### b) generates random points or evenly-spaced points on the static canvas to arrange the images ######## 481 | ### in rows or reads in the user-provided tSNE ######## 482 | ### c) generates thumbnails based on border choices, pastes the thumbnails onto the static canvas ######## 483 | ### d) stores the thumbnails in the output folder ######## 484 | ### e) updates the hover tool with thumbnail paths ######## 485 | ### f) shuffle or no shuffle option is handled in this function where images are randomly shuffled ######## 486 | ########################################################################################################### 487 | 488 | def generate_image_tSNE(chk_box_marker,rb_val,rb_rs_val,rb_shf_val, rb_imtech_val, mc, wc, LABELS_MARKERS): 489 | full_image = Image.new('RGBA', (width, height)) 490 | size = [256,256] 491 | 492 | file_name_hover = [] 493 | file_name_hover_list = [] 494 | 495 | if (rb_shf_val == 0): # no shuffle option 496 | 497 | 498 | if(rb_rs_val==1): 499 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), index_col=None, header= None) 500 | print('usertsne') 501 | elif(rb_rs_val==0): 502 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 503 | print('origtsne') 504 | 505 | elif(rb_rs_val==2): 506 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), index_col=None, header= None) 507 | print('seealltsne') 508 | 509 | tsne = np.array(df_Xtsne) 510 | 511 | ''' 512 | #create the random tsne projections 513 | if (rb_rs_val==1): 514 | # Choose up to k points around each reference point as candidates for a new 515 | # sample point 516 | k = 10 517 | 518 | # Minimum distance between samples 519 | r = 1.7 520 | 521 | width_1, height_1 = 20, 22 522 | 523 | print('Generating random co-ordinates') 524 | 525 | # Cell side length 526 | a = r/np.sqrt(2) 527 | # Number of cells in the x- and y-directions of the grid 528 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 529 | 530 | # A list of coordinates in the grid of cells 531 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 532 | # Initilalize the dictionary of cells: each key is a cell's coordinates, the 533 | # corresponding value is the index of that cell's point's coordinates in the 534 | # samples list (or None if the cell is empty). 535 | cells = {coords: None for coords in coords_list} 536 | 537 | 538 | 539 | # Pick a random point to start with. 540 | pt = (np.random.uniform(0, width_1), np.random.uniform(0, height_1)) 541 | samples = [pt] 542 | # Our first sample is indexed at 0 in the samples list... 543 | cells[get_cell_coords(pt,a)] = 0 544 | # ... and it is active, in the sense that we're going to look for more points 545 | # in its neighbourhood. 546 | active = [0] 547 | 548 | nsamples = 1 549 | # As long as there are points in the active list, keep trying to find samples. 550 | while (nsamples < num_images): #active: 551 | # choose a random "reference" point from the active list. 552 | idx = np.random.choice(active) 553 | refpt = samples[idx] 554 | # Try to pick a new point relative to the reference point. 555 | pt = get_point(k, refpt,r,a,nx,ny,cells,samples) 556 | if pt: 557 | # Point pt is valid: add it to the samples list and mark it as active 558 | samples.append(pt) 559 | nsamples += 1 560 | active.append(len(samples)-1) 561 | cells[get_cell_coords(pt,a)] = len(samples) - 1 562 | print('nsamples is: ',str(nsamples)) 563 | else: 564 | # We had to give up looking for valid points near refpt, so remove it 565 | # from the list of "active" points. 566 | active.remove(idx) 567 | 568 | tsne = np.asarray(samples) 569 | df_Xtsne = pd.DataFrame(tsne) 570 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), header=None, index=None) 571 | 572 | 573 | elif(rb_rs_val==0): 574 | 575 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 576 | df_Xtsne.shape 577 | tsne = np.array(df_Xtsne ) 578 | 579 | elif(rb_rs_val==2): 580 | 581 | # Choose up to k points around each reference point as candidates for a new 582 | # sample point 583 | k = 10 584 | 585 | # Minimum distance between samples 586 | r = 2 587 | 588 | width_1, height_1 = 20, 22 589 | 590 | print('Arrange images side-by-side') 591 | 592 | # Cell side length 593 | a = r/np.sqrt(2) 594 | # Number of cells in the x- and y-directions of the grid 595 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 596 | 597 | # A list of coordinates in the grid of cells 598 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 599 | 600 | 601 | #logic to get grid points 602 | m = np.int(np.floor(nx*ny/num_images)) 603 | 604 | 605 | row_needed = [] 606 | def multiples(m, num_images): 607 | for i in range(num_images): 608 | row_needed.append(i*m) 609 | 610 | multiples(m,num_images) 611 | 612 | 613 | 614 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 615 | 616 | print(type(select_coords)) 617 | ################ 618 | 619 | #tsne = np.asarray(coords_list) 620 | tsne = select_coords 621 | df_Xtsne = pd.DataFrame(tsne) 622 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), header=None, index=None) 623 | 624 | 625 | ''' 626 | # save the tSNE points to bve read later on irrespective of whoch option was chosen 627 | df_Xtsne_touse = pd.DataFrame(tsne) 628 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse.csv'), header=None, index=None) 629 | 630 | tx, ty = tsne[:,0], tsne[:,1] 631 | 632 | tx[tx ==0] = 0.2 633 | ty[ty ==0] = 0.1 634 | 635 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 636 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 637 | 638 | 639 | 640 | ##identify the markers 641 | mm = np.asarray(LABELS_MARKERS)[chk_box_marker] 642 | 643 | tiles = [] 644 | 645 | 646 | marker_choice = np.array(mc)[chk_box_marker] 647 | weight_choice = np.array(wc)[chk_box_marker] 648 | 649 | 650 | 651 | inds = range(len(marker_image_list)) 652 | 653 | N = len(inds) 654 | 655 | 656 | for k in range(len(inds)): 657 | 658 | image_all_2 = [] 659 | image_all_1 = [] 660 | 661 | 662 | 663 | 664 | im = tifffile.imread(marker_image_list[inds[k]]) 665 | if (rb_imtech_val ==2): #for codex 666 | im = im.reshape(64,5040,9408) 667 | 668 | 669 | 670 | for m in range(len(marker_choice)): 671 | image = im[marker_choice[m]] 672 | 673 | median_filtered = scipy.ndimage.median_filter(image, size=1) 674 | image_all_2.append(median_filtered) 675 | 676 | 677 | # apply threshold 678 | 679 | thresh = threshold_otsu(image_all_2[m]) 680 | 681 | 682 | 683 | 684 | print('thresh is: ', str(thresh)) 685 | 686 | bw = closing(image > thresh, square(1)) 687 | 688 | # remove artifacts connected to image border 689 | cleared_imtsne = clear_border(bw) 690 | 691 | 692 | image_all_1.append(cleared_imtsne*weight_choice[m]) 693 | 694 | tl = sum(image_all_1) 695 | 696 | 697 | 698 | if (rb_imtech_val ==0): #vectra 699 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) 700 | 701 | elif (rb_imtech_val ==1): #t-CyCIF 702 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 703 | elif (rb_imtech_val ==2): # CODEX 704 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 705 | elif (rb_imtech_val ==3): # CyCIF 706 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 707 | 708 | old_size = tile_1.size 709 | 710 | 711 | 712 | 713 | if(rb_val==0): 714 | new_size = (old_size[0]+1, old_size[1]+1) 715 | new_im = Image.new("RGB", new_size,color='black') 716 | 717 | elif(rb_val==1): # for the border #response based 718 | new_size = (old_size[0]+50, old_size[1]+50) 719 | 720 | 721 | 722 | new_im = Image.new("RGB", new_size,color_vec[inds[k]])## color='yellow') 723 | 724 | elif(rb_val==2): # for the border #treatment based 725 | new_size = (old_size[0]+50, old_size[1]+50) 726 | 727 | 728 | new_im = Image.new("RGB", new_size,color_vec_tx[inds[k]])# color='yellow') 729 | 730 | elif(rb_val==3): # for the border #cluster based 731 | new_size = (old_size[0]+50, old_size[1]+50) 732 | 733 | 734 | new_im = Image.new("RGB", new_size,color_vec_clasgn[inds[k]])# colours_58[cx])# color='yellow') 735 | 736 | 737 | elif(rb_val==4): # for the border #patient id based 738 | new_size = (old_size[0]+50, old_size[1]+50) 739 | 740 | 741 | new_im = Image.new("RGB", new_size, color_vec_patid[inds[k]])# color='yellow') 742 | 743 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 744 | 745 | 746 | 747 | file_name = os.path.join(path_wd+'/output_tiles/image_tsne_tils'+str(k)+'.png') 748 | 749 | file_name_hover.append('image_tsne_tils'+str(k)+'.png') 750 | 751 | new_im.save(file_name) 752 | 753 | # Read Images 754 | 755 | 756 | tile_2 = Image.open(file_name) 757 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 758 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 759 | 760 | full_image.paste(tile_2, (int((width-max_dim)*ty[k]), int((height-max_dim)*tx[k])),mask=tile_2.convert('RGBA')) 761 | 762 | 763 | 764 | matplotlib.pyplot.figure(figsize = (25,20)) 765 | plt.imshow(full_image) 766 | plt.axis('off') 767 | 768 | 769 | 770 | 771 | k = random.randint(2,500) 772 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k)+'.png') 773 | full_image.save(file_name) 774 | 775 | 776 | 777 | rotated_img = full_image.rotate(90) 778 | file_name_rot = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k)+'_rot.png') 779 | 780 | 781 | rotated_img.save(file_name_rot) 782 | 783 | 784 | 785 | 786 | ##code for reshuffling 787 | 788 | elif (rb_shf_val==1): # shuffle images 789 | 790 | #pick the tsne file to start with 791 | if(rb_rs_val==1): 792 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), index_col=None, header= None) 793 | print('usertsne') 794 | elif(rb_rs_val==0): 795 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 796 | print('origtsne') 797 | 798 | elif(rb_rs_val==2): 799 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), index_col=None, header= None) 800 | print('seealltsne') 801 | 802 | tsne = np.array(df_Xtsne) 803 | 804 | 805 | 806 | # save the tSNE points to be read later on, irrespective of which option was chosen 807 | df_Xtsne_touse = pd.DataFrame(tsne) 808 | df_Xtsne_touse.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_touse.csv'), header=None, index=None) 809 | 810 | 811 | tx, ty = tsne[:,0], tsne[:,1] 812 | 813 | tx[tx ==0] = 0.2 814 | ty[ty ==0] = 0.1 815 | 816 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 817 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 818 | 819 | full_image = Image.new('RGBA', (width, height)) 820 | 821 | size = [256,256] 822 | 823 | 824 | ##identify the markers 825 | mm = np.asarray(LABELS_MARKERS)[chk_box_marker] 826 | 827 | tiles = [] 828 | 829 | 830 | marker_choice = np.array(mc)[chk_box_marker] 831 | weight_choice = np.array(wc)[chk_box_marker] 832 | 833 | inds = range(len(marker_image_list)) 834 | 835 | N = len(inds) 836 | shf_N = np.array(range(N)) 837 | random.shuffle(shf_N) 838 | print("#############shuffle file order###") 839 | print(shf_N) 840 | count_file = 0 841 | 842 | for k in range(N): 843 | 844 | image_all_2 = [] 845 | image_all_1 = [] 846 | 847 | 848 | 849 | 850 | im = tifffile.imread(marker_image_list[shf_N[k]]) 851 | if (rb_imtech_val ==2): #for codex 852 | im = im.reshape(64,5040,9408) ## 853 | 854 | for m in range(len(marker_choice)): 855 | image = im[marker_choice[m]] 856 | 857 | median_filtered = scipy.ndimage.median_filter(image, size=1) 858 | image_all_2.append(median_filtered) 859 | 860 | 861 | 862 | # apply threshold 863 | 864 | thresh = threshold_otsu(image_all_2[m]) 865 | 866 | 867 | print(thresh) 868 | 869 | bw = closing(image > thresh, square(1)) 870 | 871 | # remove artifacts connected to image border 872 | cleared_imtsne = clear_border(bw) 873 | 874 | 875 | image_all_1.append(cleared_imtsne*weight_choice[m]) 876 | 877 | tl = sum(image_all_1) 878 | 879 | 880 | if (rb_imtech_val ==0): #vectra 881 | tile_1 = Image.fromarray(np.uint8(cm.viridis(tl)*255)) 882 | 883 | elif (rb_imtech_val ==1): #t-CyCIF 884 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 885 | elif (rb_imtech_val ==2): # CODEX 886 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 887 | elif (rb_imtech_val ==3): # CyCIF 888 | tile_1 = Image.fromarray(np.uint8(cm.jet(tl)*255)) 889 | 890 | old_size = tile_1.size 891 | 892 | print(pat_ind_list[shf_N[k]]) 893 | 894 | 895 | if(rb_val==0): 896 | new_size = (old_size[0]+1, old_size[1]+1) 897 | new_im = Image.new("RGB", new_size,color='black') 898 | 899 | elif(rb_val==1): # for the border #response based 900 | new_size = (old_size[0]+50, old_size[1]+50) 901 | 902 | 903 | 904 | 905 | new_im = Image.new("RGB", new_size,color_vec[shf_N[k]])# color='yellow') 906 | 907 | elif(rb_val==2): # for the border #treatment based 908 | new_size = (old_size[0]+50, old_size[1]+50) 909 | 910 | new_im = Image.new("RGB", new_size,color_vec_tx[shf_N[k]])# color='yellow') 911 | 912 | elif(rb_val==3): # for the border #cluster based 913 | new_size = (old_size[0]+50, old_size[1]+50) 914 | 915 | 916 | new_im = Image.new("RGB", new_size,color_vec_clasgn[shf_N[k]])# color='yellow') 917 | 918 | 919 | elif(rb_val==4): # for the border #patient id based 920 | new_size = (old_size[0]+50, old_size[1]+50) 921 | 922 | 923 | new_im = Image.new("RGB", new_size,color_vec_patid[shf_N[k]])# color='yellow') 924 | 925 | 926 | new_im.paste(tile_1, (int((new_size[0]-old_size[0])/2),int((new_size[1]-old_size[1])/2))) 927 | 928 | 929 | count_file = np.int(np.array(np.where(shf_N[k]==shf_N)).flatten()) 930 | 931 | file_name = os.path.join(path_wd+'/output_tiles/image_tsne_tils'+str(shf_N[k])+'.png') 932 | 933 | file_name_hover.append('image_tsne_tils'+str(shf_N[k])+'.png') 934 | 935 | 936 | new_im.save(file_name) 937 | 938 | 939 | 940 | # Read Images 941 | 942 | 943 | tile_2 = Image.open(file_name) 944 | rs = max(1, tile_2.width/max_dim, tile_2.height/max_dim) 945 | tile_2 = tile_2.resize((int(tile_2.width/rs), int(tile_2.height/rs)), Image.ANTIALIAS) #commented antialias 946 | 947 | 948 | full_image.paste(tile_2, (int((width-max_dim)*ty[shf_N[k]]), int((height-max_dim)*tx[shf_N[k]])),mask=tile_2.convert('RGBA')) 949 | 950 | 951 | matplotlib.pyplot.figure(figsize = (25,20)) 952 | plt.imshow(full_image) 953 | plt.axis('off') 954 | 955 | 956 | 957 | ## order filenamehover list before sending it out to the live panels 958 | so = np.argsort(shf_N) 959 | file_name_hover_np = np.array(file_name_hover) 960 | sorted_file_name_hover = file_name_hover_np[so] 961 | file_name_hover = sorted_file_name_hover.tolist() 962 | 963 | 964 | k1 = random.randint(2,500) 965 | file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k1)+'.png') 966 | 967 | full_image.save(file_name) 968 | 969 | 970 | rotated_img = full_image.rotate(90) 971 | file_name_rot = file_name = os.path.join(path_wd+'/image_tSNE_GUI/static/image_tsne_tils_all_'+str(k1)+'_rot.png') 972 | 973 | 974 | 975 | rotated_img.save(file_name_rot) 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | return([file_name_rot,tsne, file_name_hover]) 985 | 986 | 987 | ############################################################################### 988 | ### button_callback() #### 989 | ### a) Calls the create_figure() that collects user inputs from the GUI #### 990 | ### b) draw_tSNE_scatter() generates the tSNE plots for the live canvases #### 991 | ### b) reads in the user-provided tSNE #### 992 | ### c) generates thumbnails, and pastes these onto the static canvas #### 993 | ### d) stores the thumbnails in the output folder #### 994 | ### e) updates the hover tool with thumbnail paths #### 995 | ############################################################################### 996 | 997 | 998 | def button_callback(): 999 | 1000 | theme_t = theme_select.value 1001 | if(theme_t == 'black'): 1002 | curdoc().theme= theme_black 1003 | elif(theme_t == 'gray'): 1004 | curdoc().theme= theme_gray 1005 | elif(theme_t == 'dark blue'): 1006 | curdoc().theme= theme_blue 1007 | 1008 | jk = create_figure(stack_montage_flag) 1009 | layout.children[1] = jk[0] 1010 | 1011 | print('############') 1012 | print('In Button callback') 1013 | tsne3 = jk[1] 1014 | print(type(tsne3)) 1015 | print(tsne3) 1016 | 1017 | 1018 | file_name_hover = jk[2] 1019 | print('############fnh in button callback') 1020 | print('file_name_hover') 1021 | print(file_name_hover) 1022 | 1023 | 1024 | 1025 | markers_single = jk[3] 1026 | print('############') 1027 | print(type(markers_single)) 1028 | print(markers_single) 1029 | 1030 | cluster_ms_list = jk[4] 1031 | print('############') 1032 | print(type(cluster_ms_list )) 1033 | print(cluster_ms_list ) 1034 | 1035 | p1_out = draw_tSNE_scatter(tsne3, file_name_hover, cluster_ms_list) 1036 | p1 = p1_out[0] 1037 | p2 = p1_out[1] 1038 | p3 = p1_out[2] 1039 | p4 = p1_out[3] 1040 | 1041 | source = p1_out[4] 1042 | 1043 | tab1 = Panel(child=p1, title="Response") 1044 | tab2 = Panel(child=p2, title="Treatment") 1045 | tab3 = Panel(child=p3, title="Cluster Annotations") 1046 | tab4 = Panel(child=p4, title="Patient id") 1047 | 1048 | tabs = Tabs(tabs=[ tab1, tab2, tab3, tab4 ]) 1049 | layout.children[2] = tabs 1050 | 1051 | return([p,p1,p2,p3,p4,source,tabs]) 1052 | 1053 | 1054 | ############################################################################ 1055 | ### create_figure() collects the user choices from the GUI and #### 1056 | ### calls either the generate_stack_montage() for reading in #### 1057 | ### a single image or the generate_image_tSNE() for multiplexed images #### 1058 | ############################################################################ 1059 | 1060 | 1061 | 1062 | def create_figure(stack_montage_flag): 1063 | 1064 | p = figure(tools=TOOLS,x_range=x_range, y_range=y_range,width=1000,height=1000) 1065 | 1066 | rb_imtech = radio_button_group_imtech.value 1067 | 1068 | rb = radio_button_group.value 1069 | 1070 | rb_rs = radio_button_group_RS.value 1071 | 1072 | rb_shf = radio_button_group_Shf.value 1073 | 1074 | chk_box_marker = checkbox_group.active 1075 | 1076 | print(chk_box_marker) 1077 | 1078 | chk_box_marker_sm = checkbox_group_sm.active 1079 | print('**chk_box_marker_sm**') 1080 | print(chk_box_marker_sm) 1081 | 1082 | 1083 | if(rb=='No'): 1084 | rb_val = 0 1085 | elif(rb=='Based on Response'): 1086 | rb_val = 1 1087 | elif(rb=='Based on Treatment'): 1088 | rb_val = 2 1089 | elif(rb=='Based on Clusters'): 1090 | rb_val = 3 1091 | elif(rb=='Based on Patient id'): 1092 | rb_val = 4 1093 | 1094 | 1095 | 1096 | if(rb_rs=='Generate random co-ordinates'): 1097 | rb_rs_val = 1 1098 | elif(rb_rs=='Use t-SNE co-ordinates'): 1099 | rb_rs_val = 0 1100 | elif(rb_rs == 'Arrange in rows'): 1101 | rb_rs_val = 2 1102 | 1103 | 1104 | if(rb_shf=='Yes'): 1105 | rb_shf_val = 1 1106 | else: 1107 | rb_shf_val = 0 1108 | 1109 | 1110 | 1111 | if (rb_imtech == 'Vectra'): 1112 | rb_imtech_val = 0 1113 | elif (rb_imtech=='t-CyCIF'): 1114 | rb_imtech_val = 1 1115 | elif (rb_imtech=='CODEX'): 1116 | rb_imtech_val = 2 1117 | elif (rb_imtech=='CyCIF'): 1118 | rb_imtech_val = 3 1119 | 1120 | ## adding in the codex, vectra, tcycif, cycif options 1121 | # collecting the marker names for tcycif/codex stack montage 1122 | 1123 | cluster_ms_list = ['nil'] * len(LABELS_MARKERS)#[] 1124 | clust_ms_list = [] 1125 | clust_ms_list_1 = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/markers.csv'), 1126 | header= 0,index_col=None) 1127 | markers_single = np.array(clust_ms_list_1.iloc[:,2]).flatten() 1128 | 1129 | 1130 | ## need to update the markers_single array based on the order the files are read in 1131 | if (stack_montage_flag==True): 1132 | if (rb_imtech_val ==1): #SM for t-CyCIF data 1133 | print('in SM for t-CyCIF') 1134 | ms_list=[] 1135 | cluster_ms_list = [] 1136 | for i in range(markers_single.shape[0]): 1137 | print(i) 1138 | print(pat_fov_list[i]) 1139 | print(markers_single) 1140 | channel_num = np.int(pat_fov_list[i].split("_40X_")[1].split(".tiff")[0]) 1141 | ms_list.append(markers_single[channel_num-1]) 1142 | 1143 | 1144 | markers_single = np.copy(np.array(ms_list)) 1145 | 1146 | 1147 | 1148 | # use this updated markers_single name order going forward 1149 | for i in range(markers_single.shape[0]): 1150 | 1151 | cluster_ms_list.append('Channel '+ markers_single[i]) 1152 | 1153 | elif (rb_imtech_val ==2): #SM for CODEX 1154 | ## for codex sm for tonsils 1155 | print('in SM for CODEX') 1156 | ## need to update the markers_single array based on the order the files are read in 1157 | ms_list=[] 1158 | cluster_ms_list = [] 1159 | for i in range(markers_single.shape[0]): 1160 | print(i) 1161 | print(pat_fov_list[i]) 1162 | 1163 | channel_name = pat_fov_list[i].split('.tif')[0].split('_')[3] 1164 | channel_num = np.int(np.array(np.where(channel_name==markers_single)).flatten()) 1165 | ms_list.append(markers_single[channel_num]) 1166 | 1167 | markers_single = np.copy(np.array(ms_list)) 1168 | 1169 | 1170 | # use this updated markers_single name order going forward 1171 | for i in range(markers_single.shape[0]): 1172 | 1173 | cluster_ms_list.append('Channel '+ markers_single[i]) 1174 | 1175 | 1176 | elif (stack_montage_flag == False): 1177 | 1178 | 1179 | # to handle all markers in t-CyCIF/CODEX/Vectra 1180 | 1181 | mc = [] 1182 | for m in range(len(LABELS_MARKERS)): 1183 | in_mc = np.int(np.array(np.where(LABELS_MARKERS[m] == np.array(markers_single))).flatten()) 1184 | mc.append(in_mc) 1185 | 1186 | if (rb_imtech_val ==0):#Vectra 1187 | wc = [100] * len(LABELS_MARKERS) 1188 | wc[0] = 800 1189 | wc[1]=wc[2] = 400 1190 | elif (rb_imtech_val ==1):#t-CyCIF 1191 | wc = [100] * len(LABELS_MARKERS) 1192 | wc[0] = 50 1193 | elif (rb_imtech_val ==2): 1194 | wc = [150] * len(LABELS_MARKERS) 1195 | wc[0] = 300 # 1196 | wc[6] = 100 1197 | 1198 | elif (rb_imtech_val ==3): 1199 | wc = [150] * len(LABELS_MARKERS) 1200 | wc[1] = 100 1201 | wc[2] = 300 1202 | 1203 | cluster_ms_list = ['nil'] * num_images #[] 1204 | 1205 | 1206 | 1207 | 1208 | ########### 1209 | 1210 | 1211 | if(len(chk_box_marker_sm)==0): #so we are using the multiple multiplexed option 1212 | if(len(chk_box_marker)==0): #if no markers are chosen, display defaults 1213 | 1214 | file_name_1 = "image_tSNE_GUI/static/image_tsne_tils_all.png" 1215 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1216 | df_Xtsne = pd.read_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), index_col=None, header= None) 1217 | df_Xtsne.shape 1218 | tsne_points = np.array(df_Xtsne ) 1219 | file_name_hover = list(range(num_images)) 1220 | 1221 | else: 1222 | 1223 | 1224 | out1 = generate_image_tSNE(chk_box_marker, rb_val,rb_rs_val,rb_shf_val,rb_imtech_val, mc, wc, LABELS_MARKERS) 1225 | file_name_all = out1[0] 1226 | tsne_points = out1[1] 1227 | file_name_hover = out1[2] 1228 | 1229 | print('file_name_hover after gen_image_tsne') 1230 | print(file_name_hover) 1231 | 1232 | print('#########file_name_all########') 1233 | print('#################') 1234 | 1235 | print(file_name_all) 1236 | 1237 | file_name_1 = file_name_all.split('/code')[1] 1238 | print('#########file_name_1########') 1239 | print(file_name_1) 1240 | 1241 | 1242 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1243 | 1244 | elif(len(chk_box_marker_sm)==1): #stack montage option 1245 | print('chosen stack montage') 1246 | out1sm = generate_stack_montage(chk_box_marker, rb_imtech_val, LABELS_MARKERS) 1247 | file_name_all = out1sm[0] 1248 | tsne_points = out1sm[1] 1249 | file_name_hover = out1sm[2] 1250 | 1251 | print('############fnh post sm') 1252 | print('file_name_hover') 1253 | print(file_name_hover) 1254 | 1255 | print('#########file_name_all########') 1256 | print('#################') 1257 | 1258 | print(file_name_all) 1259 | 1260 | file_name_1 = file_name_all.split('/code')[1] 1261 | print('#########file_name_1########') 1262 | print(file_name_1) 1263 | 1264 | 1265 | p.image_url(url=[file_name_1], x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0]) 1266 | 1267 | 1268 | return ([p,tsne_points, file_name_hover,markers_single, cluster_ms_list]) 1269 | 1270 | 1271 | ########## 1272 | # define the colour vectors 1273 | colours_58 = ["firebrick","gold","royalblue","green","dimgray","orchid","darkviolet", 1274 | "red", "orange", "limegreen", "blue", "purple", "seagreen","gold","darkolivegreen", 1275 | "lightpink","thistle","mistyrose","saddlebrown","slategrey","powderblue", 1276 | "palevioletred","mediumvioletred","yellowgreen","lemonchiffon","chocolate", 1277 | "lightsalmon","lightcyan","lightblue", "darkorange","black","darkblue","darkgreen","paleturquoise","yellow","rosybrown", 1278 | "steelblue","dodgerblue","darkkhaki","lime","coral","aquamarine","mediumpurple","violet","plum", 1279 | "deeppink","navy","seagreen","teal","mediumspringgreen","cadetblue", 1280 | "maroon","silver","sienna","crimson","slateblue","magenta","darkmagenta"] 1281 | 1282 | colours_resp = ["yellow","red","green"] 1283 | colours_tx = ["orange","limegreen","violet"] 1284 | 1285 | 1286 | ##################################### 1287 | #### Section that gets populated #### 1288 | ####based on User uploads ########### 1289 | ##################################### 1290 | ##################################### 1291 | ## This section reads in images, #### 1292 | ## metadata, markers in the data,#### 1293 | ## and user choices #### 1294 | ##################################### 1295 | ##################################### 1296 | 1297 | 1298 | path_wd = os.getcwd() 1299 | print('Current working directory: ') 1300 | print(path_wd) 1301 | 1302 | 1303 | 1304 | 1305 | ### to generate tsne points from the onset itself 1306 | 1307 | fname = os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv') 1308 | if os.path.isfile(fname): 1309 | df_Xtsne = pd.read_csv(fname, index_col=None, header= None) 1310 | tsne = np.array(df_Xtsne ) 1311 | tx, ty = tsne[:,0], tsne[:,1] 1312 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 1313 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 1314 | num_images = tsne.shape[0] 1315 | 1316 | 1317 | 1318 | ## to have the arrange_in_rows and random points already available 1319 | # Choose up to k points around each reference point as candidates for a new 1320 | # sample point 1321 | k = 10 1322 | 1323 | # Minimum distance between samples 1324 | r = 1.7 1325 | 1326 | width_1, height_1 = 20, 22 1327 | 1328 | print('Generating random co-ordinates') 1329 | 1330 | # Cell side length 1331 | a = r/np.sqrt(2) 1332 | # Number of cells in the x- and y-directions of the grid 1333 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1334 | 1335 | # A list of coordinates in the grid of cells 1336 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1337 | # Initilalize the dictionary of cells: each key is a cell's coordinates, the 1338 | # corresponding value is the index of that cell's point's coordinates in the 1339 | # samples list (or None if the cell is empty). 1340 | cells = {coords: None for coords in coords_list} 1341 | 1342 | 1343 | 1344 | # Pick a random point to start with. 1345 | pt = (np.random.uniform(0, width_1), np.random.uniform(0, height_1)) 1346 | samples = [pt] 1347 | # Our first sample is indexed at 0 in the samples list... 1348 | cells[get_cell_coords(pt,a)] = 0 1349 | # ... and it is active, in the sense that we're going to look for more points 1350 | # in its neighbourhood. 1351 | active = [0] 1352 | 1353 | nsamples = 1 1354 | # As long as there are points in the active list, keep trying to find samples. 1355 | while (nsamples < num_images): #active: 1356 | # choose a random "reference" point from the active list. 1357 | idx = np.random.choice(active) 1358 | refpt = samples[idx] 1359 | # Try to pick a new point relative to the reference point. 1360 | pt = get_point(k, refpt,r,a,nx,ny,cells,samples) 1361 | if pt: 1362 | # Point pt is valid: add it to the samples list and mark it as active 1363 | samples.append(pt) 1364 | nsamples += 1 1365 | active.append(len(samples)-1) 1366 | cells[get_cell_coords(pt,a)] = len(samples) - 1 1367 | print('nsamples is: ',str(nsamples)) 1368 | else: 1369 | # We had to give up looking for valid points near refpt, so remove it 1370 | # from the list of "active" points. 1371 | active.remove(idx) 1372 | 1373 | tsne = np.asarray(samples) 1374 | tx, ty = tsne[:,0], tsne[:,1] 1375 | 1376 | tx[tx ==0] = 0.2 1377 | ty[ty ==0] = 0.1 1378 | 1379 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 1380 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 1381 | 1382 | df_Xtsne_m = pd.DataFrame(tsne) 1383 | #df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), header=None, index=None) 1384 | df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), header=None, index=None) #14th march 2022 1385 | 1386 | 1387 | ##### for 'arrange in rows' option at the onset itself 1388 | 1389 | 1390 | # Minimum distance between samples 1391 | r = 2 1392 | 1393 | width_1, height_1 = 20, 22 1394 | 1395 | print('Arrange images side-by-side') 1396 | 1397 | # Cell side length 1398 | a = r/np.sqrt(2) 1399 | # Number of cells in the x- and y-directions of the grid 1400 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1401 | 1402 | # A list of coordinates in the grid of cells 1403 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1404 | 1405 | 1406 | #logic to get grid points - 29th March 2021 1407 | m = np.int(np.floor(nx*ny/num_images)) 1408 | 1409 | 1410 | row_needed = [] 1411 | def multiples(m, num_images): 1412 | for i in range(num_images): 1413 | row_needed.append(i*m) 1414 | 1415 | multiples(m,num_images) 1416 | 1417 | 1418 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 1419 | 1420 | print(type(select_coords)) 1421 | 1422 | tsne1 = select_coords 1423 | 1424 | tsne1[tsne1 ==0] = 0.2 1425 | 1426 | df_Xtsne = pd.DataFrame(tsne1) 1427 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), header=None, index=None) 1428 | 1429 | 1430 | else: 1431 | #read in images 1432 | FoV_path = os.path.join(path_wd + '/user_inputs/figures/') 1433 | count_images = 0 1434 | for fname in os.listdir(FoV_path): 1435 | print(fname) 1436 | count_images = count_images + 1 1437 | num_images = count_images 1438 | #generate random 2D coords for these images 1439 | 1440 | #### 1441 | #### tsne and clustering within mistic, if not provided by user 1442 | #### 1443 | 1444 | im1 = tifffile.imread(os.path.join(FoV_path+fname)) 1445 | 1446 | if (im1.shape[0] < 6): 1447 | num_markers = im1.shape[0] 1448 | else: 1449 | num_markers = 6 1450 | 1451 | data_CM = np.zeros((1,num_markers)) # one row = one image with mean of markers in the columns, defaults to 6 markers 1452 | 1453 | for fname in os.listdir(FoV_path): 1454 | print(fname) 1455 | im1 = tifffile.imread(os.path.join(FoV_path+fname)) 1456 | if ((im1.shape[0]==16) and (im1.shape[1]==4) and (im1.shape[2]==5040) and (im1.shape[3]==9408)): #for codex intake 1457 | im1 = im1.reshape(64,5040,9408) 1458 | 1459 | #if (rb_imtech_val ==2): #for codex 1460 | # im = im.reshape(64,5040,9408) 1461 | 1462 | 1463 | 1464 | image_summary_vec = np.zeros((1,num_markers)) 1465 | for m in range(num_markers): 1466 | image1 = im1[m] 1467 | 1468 | median_filtered1 = scipy.ndimage.median_filter(image1, size=1) 1469 | 1470 | 1471 | thresh = threshold_otsu(median_filtered1) 1472 | 1473 | 1474 | bw = closing(image1 > thresh, square(1)) 1475 | 1476 | # remove artifacts connected to image border 1477 | cleared_imtsne = clear_border(bw) 1478 | 1479 | 1480 | image_summary_vec[0,m] = np.mean(cleared_imtsne) #mean of each channel m per image fname 1481 | 1482 | 1483 | 1484 | data_CM = np.concatenate((data_CM,image_summary_vec),axis=0) 1485 | 1486 | data_CM = np.delete(data_CM, (0), axis=0) 1487 | 1488 | # perform tSNE 1489 | tsne = TSNE(n_components=2, verbose=1)#, perplexity=30, n_iter=900) 1490 | X_2d = tsne.fit_transform(data_CM) 1491 | 1492 | # Fit a Dirichlet process mixture of Gaussians using n components 1493 | bgm = BayesianGaussianMixture(n_components=3, random_state=42).fit(data_CM) 1494 | cluster_asgn = bgm.predict(data_CM) 1495 | 1496 | #save tSNE and cluster assignments 1497 | 1498 | df_Xtsne_u = pd.DataFrame(X_2d) 1499 | df_Xtsne_u.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), header=None, index=None) 1500 | 1501 | 1502 | 1503 | fname = os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv') 1504 | if (os.path.isfile(fname)==False): 1505 | df_dpgmm_u = pd.DataFrame(cluster_asgn) 1506 | df_dpgmm_u.to_csv(os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv'), header=None, index=None) 1507 | 1508 | ### end of tsne/clustering by mistic 1509 | 1510 | # Choose up to k points around each reference point as candidates for a new 1511 | # sample point 1512 | k = 10 1513 | 1514 | # Minimum distance between samples 1515 | r = 1.7 1516 | 1517 | width_1, height_1 = 20, 22 1518 | 1519 | print('Generating random co-ordinates') 1520 | 1521 | # Cell side length 1522 | a = r/np.sqrt(2) 1523 | # Number of cells in the x- and y-directions of the grid 1524 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1525 | 1526 | # A list of coordinates in the grid of cells 1527 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1528 | # Initilalize the dictionary of cells: each key is a cell's coordinates, the 1529 | # corresponding value is the index of that cell's point's coordinates in the 1530 | # samples list (or None if the cell is empty). 1531 | cells = {coords: None for coords in coords_list} 1532 | 1533 | 1534 | 1535 | # Pick a random point to start with. 1536 | pt = (np.random.uniform(0, width_1), np.random.uniform(0, height_1)) 1537 | samples = [pt] 1538 | # Our first sample is indexed at 0 in the samples list... 1539 | cells[get_cell_coords(pt,a)] = 0 1540 | # ... and it is active, in the sense that we're going to look for more points 1541 | # in its neighbourhood. 1542 | active = [0] 1543 | 1544 | nsamples = 1 1545 | # As long as there are points in the active list, keep trying to find samples. 1546 | while (nsamples < num_images): #active: 1547 | # choose a random "reference" point from the active list. 1548 | idx = np.random.choice(active) 1549 | refpt = samples[idx] 1550 | # Try to pick a new point relative to the reference point. 1551 | pt = get_point(k, refpt,r,a,nx,ny,cells,samples) 1552 | if pt: 1553 | # Point pt is valid: add it to the samples list and mark it as active 1554 | samples.append(pt) 1555 | nsamples += 1 1556 | active.append(len(samples)-1) 1557 | cells[get_cell_coords(pt,a)] = len(samples) - 1 1558 | print('nsamples is: ',str(nsamples)) 1559 | else: 1560 | # We had to give up looking for valid points near refpt, so remove it 1561 | # from the list of "active" points. 1562 | active.remove(idx) 1563 | 1564 | tsne = np.asarray(samples) 1565 | tx, ty = tsne[:,0], tsne[:,1] 1566 | 1567 | tx[tx ==0] = 0.2 1568 | ty[ty ==0] = 0.1 1569 | 1570 | tx = (tx-np.min(tx)) / (np.max(tx) - np.min(tx)) 1571 | ty = (ty-np.min(ty)) / (np.max(ty) - np.min(ty)) 1572 | 1573 | df_Xtsne_m = pd.DataFrame(tsne) 1574 | #df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE.csv'), header=None, index=None) 1575 | df_Xtsne_m.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_user.csv'), header=None, index=None) 1576 | 1577 | 1578 | ##### for 'arrange in rows' option at the onset itself 1579 | 1580 | 1581 | # Minimum distance between samples 1582 | r = 2 1583 | 1584 | width_1, height_1 = 20, 22 1585 | 1586 | print('Arrange images side-by-side') 1587 | 1588 | # Cell side length 1589 | a = r/np.sqrt(2) 1590 | # Number of cells in the x- and y-directions of the grid 1591 | nx, ny = int(width_1 / a) + 1, int(height_1 / a) + 1 1592 | 1593 | # A list of coordinates in the grid of cells 1594 | coords_list = [(ix, iy) for ix in range(nx) for iy in range(ny)] 1595 | 1596 | 1597 | #logic to get grid points - 29th March 2021 1598 | m = np.int(np.floor(nx*ny/num_images)) 1599 | 1600 | 1601 | row_needed = [] 1602 | def multiples(m, num_images): 1603 | for i in range(num_images): 1604 | row_needed.append(i*m) 1605 | 1606 | multiples(m,num_images) 1607 | 1608 | 1609 | select_coords = np.array(coords_list)[np.array(row_needed).flatten()] 1610 | 1611 | print(type(select_coords)) 1612 | 1613 | tsne1 = select_coords 1614 | 1615 | tsne1[tsne1 ==0] = 0.2 1616 | 1617 | df_Xtsne = pd.DataFrame(tsne1) 1618 | df_Xtsne.to_csv(os.path.join(path_wd + '/user_inputs/metadata/X_imagetSNE_seeall.csv'), header=None, index=None) 1619 | 1620 | ######################## 1621 | 1622 | 1623 | 1624 | # adding this for reading in data if no marker info is present 1625 | fname = os.path.join(path_wd + '/user_inputs/metadata/Marker_ids.csv') 1626 | 1627 | if os.path.isfile(fname): 1628 | df_markers = pd.read_csv(fname, index_col=None, header= None) 1629 | markers_list = np.array(df_markers ).flatten() 1630 | LABELS_MARKERS = [] 1631 | for j in range(markers_list.shape[0]): 1632 | LABELS_MARKERS.append(markers_list[j]) 1633 | stack_montage_flag = False 1634 | 1635 | 1636 | else: 1637 | LABELS_MARKERS = ['nil'] * 6 1638 | stack_montage_flag = True 1639 | 1640 | ## read in images 1641 | marker_image_list = [] 1642 | pat_fov_list = [] 1643 | FoV_path = os.path.join(path_wd + '/user_inputs/figures/') 1644 | file_order = [] 1645 | 1646 | #sorted input 1647 | for fname in sorted(os.listdir(FoV_path)): 1648 | print(fname) 1649 | marker_image_list.append(FoV_path+fname) 1650 | pat_fov_list.append(fname) 1651 | 1652 | 1653 | 1654 | print("print image list") 1655 | print(marker_image_list) 1656 | print("file order") 1657 | print(file_order) 1658 | 1659 | 1660 | 1661 | 1662 | ################################# 1663 | ################################# 1664 | # checks for user metadata inputs 1665 | # if these files are not present, gray out the tSNE or points. 1666 | 1667 | # collecting the response metadata 1668 | 1669 | 1670 | color_vec = [] 1671 | resp_list= [] 1672 | fname = os.path.join(path_wd + '/user_inputs/metadata/Response_categories.csv') 1673 | 1674 | if os.path.isfile(fname): 1675 | resp_list_1 = np.array(pd.read_csv(fname,header= None,index_col=None)).flatten() 1676 | uni_resp,counts_resp = np.unique(resp_list_1,return_counts=True) 1677 | 1678 | for i in range(resp_list_1.shape[0]): 1679 | row_t = np.int(np.array(np.where(resp_list_1[i]==uni_resp)).flatten()) 1680 | 1681 | color_vec.append(colours_resp[row_t]) 1682 | 1683 | resp_list.append(resp_list_1[i]) 1684 | else: 1685 | for i in range(num_images): 1686 | color_vec.append('gray') 1687 | resp_list.append('Response nil') 1688 | 1689 | 1690 | # collecting the treatment metadata 1691 | 1692 | color_vec_tx = [] 1693 | tx_list = [] 1694 | fname = os.path.join(path_wd + '/user_inputs/metadata/Treatment_categories.csv') 1695 | 1696 | if os.path.isfile(fname): 1697 | tx_list_1= np.array(pd.read_csv(fname,header= None,index_col=None)).flatten() 1698 | uni_tx,counts_tx = np.unique(tx_list_1,return_counts=True) 1699 | for i in range(tx_list_1.shape[0]): 1700 | row_t = np.int(np.array(np.where(tx_list_1[i]==uni_tx)).flatten()) 1701 | 1702 | color_vec_tx.append(colours_tx[row_t]) 1703 | 1704 | tx_list.append(tx_list_1[i]) 1705 | else: 1706 | for i in range(num_images): 1707 | color_vec_tx.append('gray') 1708 | tx_list.append('Treatment nil') 1709 | 1710 | 1711 | # collecting the cluster assignments metadata 1712 | 1713 | fname = os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv') 1714 | color_vec_clasgn = [] 1715 | cluster_anno_list = [] 1716 | clust_asgn_list = [] 1717 | 1718 | 1719 | if os.path.isfile(fname): 1720 | clust_asgn_list_1 = np.array(pd.read_csv(fname, header= None,index_col=None)).flatten() 1721 | for i in range(clust_asgn_list_1.shape[0]): 1722 | 1723 | color_vec_clasgn.append(colours_58[clust_asgn_list_1[i]]) 1724 | clust_asgn_list.append(clust_asgn_list_1[i]) 1725 | cluster_anno_list.append('Cluster '+ str(clust_asgn_list_1[i])) 1726 | 1727 | else: 1728 | #for bayesian clustering, if not present 1729 | 1730 | 1731 | 1732 | FoV_path = os.path.join(path_wd + '/user_inputs/figures/') 1733 | 1734 | #sorted input data add on 17th Feb 2022 1735 | for fname in sorted(os.listdir(FoV_path)): 1736 | print(fname) 1737 | 1738 | 1739 | im1 = tifffile.imread(os.path.join(FoV_path+fname)) 1740 | 1741 | if (im1.shape[0] < 6): 1742 | num_markers = im1.shape[0] 1743 | else: 1744 | num_markers = 6 1745 | 1746 | data_CM = np.zeros((1,num_markers)) # one row = one image with mean of markers in the columns, defaults to 6 markers 1747 | 1748 | for fname1 in os.listdir(FoV_path): 1749 | print(fname1) 1750 | im1 = tifffile.imread(os.path.join(FoV_path+fname1)) 1751 | 1752 | if ((im1.shape[0]==16) and (im1.shape[1]==4) and (im1.shape[2]==5040) and (im1.shape[3]==9408)): #for codex intake 1753 | im1 = im1.reshape(64,5040,9408) 1754 | 1755 | image_summary_vec = np.zeros((1,num_markers)) 1756 | for m in range(num_markers): 1757 | image1 = im1[m] 1758 | 1759 | median_filtered1 = scipy.ndimage.median_filter(image1, size=1) 1760 | 1761 | 1762 | thresh = threshold_otsu(median_filtered1) 1763 | 1764 | 1765 | bw = closing(image1 > thresh, square(1)) 1766 | 1767 | # remove artifacts connected to image border 1768 | cleared_imtsne = clear_border(bw) 1769 | 1770 | 1771 | image_summary_vec[0,m] = np.mean(cleared_imtsne) #mean of each channel m per image fname 1772 | 1773 | 1774 | 1775 | data_CM = np.concatenate((data_CM,image_summary_vec),axis=0) 1776 | 1777 | data_CM = np.delete(data_CM, (0), axis=0) 1778 | 1779 | 1780 | 1781 | # Fit a Dirichlet process mixture of Gaussians using n components 1782 | bgm = BayesianGaussianMixture(n_components=3, random_state=42).fit(data_CM) 1783 | cluster_asgn = bgm.predict(data_CM) 1784 | 1785 | df_dpgmm_u = pd.DataFrame(cluster_asgn) 1786 | df_dpgmm_u.to_csv(os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv'), header=None, index=None) 1787 | 1788 | 1789 | fname2 = os.path.join(path_wd + '/user_inputs/metadata/Cluster_categories.csv') 1790 | 1791 | 1792 | clust_asgn_list_2 = np.array(pd.read_csv(fname2, header= None,index_col=None)).flatten() 1793 | for i in range(num_images): 1794 | 1795 | color_vec_clasgn.append(colours_58[clust_asgn_list_2[i]]) 1796 | clust_asgn_list.append(clust_asgn_list_2[i]) 1797 | cluster_anno_list.append('Cluster '+ str(clust_asgn_list_2[i])) 1798 | 1799 | ## bayes clustering end 1800 | 1801 | 1802 | 1803 | # collecting the patient id metadata 1804 | 1805 | fname = os.path.join(path_wd + '/user_inputs/metadata/Patient_ids.csv') 1806 | color_vec_patid = [] 1807 | cluster_pat_list = [] 1808 | clust_patid_list = [] 1809 | 1810 | if os.path.isfile(fname): 1811 | 1812 | 1813 | # collecting the Patient id metadata 1814 | 1815 | clust_patid_list_1 = np.array(pd.read_csv(fname,header= None,index_col=None)).flatten() 1816 | pat_ind_list = np.copy(clust_patid_list_1) 1817 | 1818 | for i in range(clust_patid_list_1.shape[0]): 1819 | 1820 | color_vec_patid.append(colours_58[clust_patid_list_1[i]]) 1821 | clust_patid_list.append(clust_patid_list_1[i]) 1822 | cluster_pat_list.append('Patient '+ str(clust_patid_list_1[i])) 1823 | 1824 | else: 1825 | pat_ind_list = [] 1826 | for i in range(num_images): 1827 | color_vec_patid.append('gray') 1828 | clust_patid_list.append('nil') 1829 | cluster_pat_list.append('Patient nil') 1830 | pat_ind_list.append('nil') 1831 | 1832 | # generate dummy thumbnail names for hover panel 1833 | 1834 | file_name_hover = [] 1835 | file_name_hover_list = [] 1836 | 1837 | for i in range(num_images): #clust_patid_list_1.shape[0]): 1838 | 1839 | file_name_hover.append(str(i)) 1840 | file_name_hover_list.append('Thumbnail '+ str(i)) 1841 | 1842 | 1843 | 1844 | 1845 | 1846 | ################# 1847 | # set up widgets 1848 | ################# 1849 | 1850 | RB_1 = ['Based on Response', 'Based on Treatment','Based on Clusters','Based on Patient id','No'] 1851 | 1852 | RB_2 = ['Generate random co-ordinates', 'Use t-SNE co-ordinates', 'Arrange in rows'] 1853 | 1854 | RB_3 = ['Yes', 'No'] 1855 | 1856 | RB_4 = ['Vectra', 't-CyCIF', 'CODEX', 'CyCIF'] 1857 | 1858 | TS_1 = ['black','gray', 'dark blue'] 1859 | 1860 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,save,box_select," 1861 | 1862 | x_range=(0,1) 1863 | y_range=(0,1) 1864 | 1865 | 1866 | #main image tSNE canvas 1867 | p = figure(tools=TOOLS,x_range=x_range, y_range=y_range,width=1000,height=1000) 1868 | tsne_points = np.zeros([1,2]) 1869 | 1870 | #additional tSNE scatter plot canvas 1871 | 1872 | 1873 | 1874 | point_tSNE = figure(plot_width=350, plot_height=350, 1875 | tools='hover,pan,wheel_zoom,box_select,reset') 1876 | point_tSNE.title.text = 'tSNE point cloud for Patient response' 1877 | 1878 | 1879 | point_tSNE.scatter(tsne[:,0],tsne[:,1],fill_alpha=0.6, color ='red',size=8,legend='Response') 1880 | 1881 | point_tSNE.legend.location = "bottom_left" 1882 | 1883 | theme_black = Theme(json={ 1884 | 'attrs': { 1885 | 'Figure': { 1886 | 'background_fill_color': '#2F2F2F', 1887 | 'border_fill_color': '#2F2F2F', 1888 | 'outline_line_color': '#444444' 1889 | }, 1890 | 'Axis': { 1891 | 'axis_line_color': "white", 1892 | 'axis_label_text_color': "white", 1893 | 'major_label_text_color': "white", 1894 | 'major_tick_line_color': "white", 1895 | 'minor_tick_line_color': "white", 1896 | 'minor_tick_line_color': "white" 1897 | }, 1898 | 'Grid': { 1899 | 'grid_line_dash': [6, 4], 1900 | 'grid_line_alpha': .3 1901 | }, 1902 | 'Circle': { 1903 | 'fill_color': 'lightblue', 1904 | 'size': 10, 1905 | }, 1906 | 'Title': { 1907 | 'text_color': "white" 1908 | } 1909 | } 1910 | }) 1911 | 1912 | theme_gray = Theme(json={ 1913 | 'attrs': { 1914 | 'Figure': { 1915 | 'background_fill_color': '#555555', 1916 | 'border_fill_color': '#2F2F2F', 1917 | 'outline_line_color': '#444444' 1918 | }, 1919 | 'Axis': { 1920 | 'axis_line_color': "white", 1921 | 'axis_label_text_color': "white", 1922 | 'major_label_text_color': "white", 1923 | 'major_tick_line_color': "white", 1924 | 'minor_tick_line_color': "white", 1925 | 'minor_tick_line_color': "white" 1926 | }, 1927 | 'Grid': { 1928 | 'grid_line_dash': [6, 4], 1929 | 'grid_line_alpha': .3 1930 | }, 1931 | 'Circle': { 1932 | 'fill_color': 'lightblue', 1933 | 'size': 10, 1934 | }, 1935 | 'Title': { 1936 | 'text_color': "white" 1937 | } 1938 | } 1939 | }) 1940 | 1941 | theme_blue = Theme(json={ 1942 | 'attrs': { 1943 | 'Figure': { 1944 | 'background_fill_color': '#25256d', 1945 | 'border_fill_color': '#2F2F2F', 1946 | 'outline_line_color': '#444444' 1947 | }, 1948 | 'Axis': { 1949 | 'axis_line_color': "white", 1950 | 'axis_label_text_color': "white", 1951 | 'major_label_text_color': "white", 1952 | 'major_tick_line_color': "white", 1953 | 'minor_tick_line_color': "white", 1954 | 'minor_tick_line_color': "white" 1955 | }, 1956 | 'Grid': { 1957 | 'grid_line_dash': [6, 4], 1958 | 'grid_line_alpha': .3 1959 | }, 1960 | 'Circle': { 1961 | 'fill_color': 'lightblue', 1962 | 'size': 10, 1963 | }, 1964 | 'Title': { 1965 | 'text_color': "white" 1966 | } 1967 | } 1968 | }) 1969 | 1970 | ######### 1971 | ######### 1972 | 1973 | 1974 | TOOLS="hover,pan,crosshair,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select," 1975 | 1976 | 1977 | TOOLTIPS = [ 1978 | ("index", "$index"), 1979 | ("(x,y)", "($x, $y)"), 1980 | ("Pat_id", "@pat_list"), 1981 | ("Response", "@res_list"), 1982 | ("Treatment", "@tx_list"), 1983 | ("Cluster id", "@clust_asgn_list"), 1984 | ("Channel", "@cluster_ms_list"), 1985 | ("Thumbnail", "@file_name_hover_list"), 1986 | ("FoV","@fov_list") 1987 | ] 1988 | 1989 | p1 = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,tools = TOOLS, 1990 | title="Patient response") 1991 | 1992 | 1993 | 1994 | 1995 | ##################################################################################### 1996 | ## This block prepares and sets up the GUI layout, and collects user-input choices ## 1997 | ##################################################################################### 1998 | 1999 | 2000 | desc = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/desc.html')).read(), sizing_mode="stretch_width") 2001 | 2002 | 2003 | desc_SM1 = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/descSM1.html')).read(), sizing_mode="stretch_width") 2004 | 2005 | desc_SM = Div(text=open(os.path.join(path_wd + '/image_tSNE_GUI/descMontage.html')).read(), sizing_mode="stretch_width") 2006 | 2007 | 2008 | 2009 | radio_button_group_imtech = Select(value='Vectra', 2010 | title='', 2011 | width=200, 2012 | options=RB_4) 2013 | 2014 | 2015 | radio_button_group = Select(value='No', 2016 | title='Image border', 2017 | width=200, 2018 | options=RB_1) 2019 | 2020 | radio_button_group_RS = Select(value='Generate new co-ordinates', 2021 | title='tSNE co-ordinates', 2022 | width=220, 2023 | options=RB_2) 2024 | 2025 | radio_button_group_Shf = Select(value='Yes', 2026 | title='Shuffle images', 2027 | width=200, 2028 | options=RB_3) 2029 | 2030 | theme_select = Select(value = 'black', 2031 | title='Theme', 2032 | width = 200, 2033 | options = TS_1) 2034 | 2035 | 2036 | checkbox_group = CheckboxGroup(labels=LABELS_MARKERS)#, active=[0, 1]) 2037 | 2038 | 2039 | button = Button(label='Run', width=100, button_type="success") 2040 | 2041 | ## added to activate Run button 2042 | button.on_click(button_callback) 2043 | 2044 | ## for stack montage 2045 | SM = ['Stack montage'] 2046 | checkbox_group_sm = CheckboxGroup(labels=SM) 2047 | 2048 | 2049 | print('updating new p1#') 2050 | 2051 | 2052 | 2053 | # set up layout and GUI refresh after every 'Run' click 2054 | 2055 | out2 = create_figure(stack_montage_flag) 2056 | print(out2) 2057 | p = out2[0] 2058 | tsne2 = out2[1] 2059 | print('############') 2060 | print(type(tsne2)) 2061 | print(tsne2) 2062 | 2063 | file_name_hover = out2[2] 2064 | print('############fnh after create figure') 2065 | print(type(file_name_hover)) 2066 | print(file_name_hover) 2067 | 2068 | 2069 | markers_single = out2[3] 2070 | print('############') 2071 | print(type(markers_single)) 2072 | print(markers_single) 2073 | 2074 | cluster_ms_list = out2[4] 2075 | print('############') 2076 | print(type(cluster_ms_list )) 2077 | print(cluster_ms_list ) 2078 | 2079 | 2080 | p11_out = draw_tSNE_scatter(tsne2, file_name_hover,cluster_ms_list ) 2081 | 2082 | p1 = p11_out[0] 2083 | p2 = p11_out[1] 2084 | p3 = p11_out[2] 2085 | p4 = p11_out[3] 2086 | 2087 | tab1 = Panel(child=p1, title="Response") 2088 | tab2 = Panel(child=p2, title="Treatment") 2089 | tab3 = Panel(child=p3, title="Cluster Annotations") 2090 | tab4 = Panel(child=p4, title="Patient id") 2091 | 2092 | tabs = Tabs(tabs=[ tab1, tab2, tab3, tab4 ]) # (added tab4) 2093 | 2094 | 2095 | 2096 | selects = column(desc, radio_button_group_imtech, desc_SM1, checkbox_group_sm, desc_SM, checkbox_group, radio_button_group, radio_button_group_RS, radio_button_group_Shf, theme_select, button, width=520) # 2097 | 2098 | layout=row(selects,p, tabs)#,selected_points)#create_figure()) 2099 | 2100 | #doc = curdoc() 2101 | curdoc().theme= theme_black 2102 | 2103 | 2104 | # add to document 2105 | curdoc().add_root(layout) 2106 | 2107 | curdoc().title = "Mistic: Image tSNE viewer" 2108 | 2109 | 2110 | 2111 | # cd image_tSNE_code/bokeh_GUI/bokeh-branch-2.3/examples/app 2112 | # bokeh serve --port 5098 --show image_tSNE_GUI 2113 | 2114 | -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/static/image_tsne_tils_all_3_rot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MathOnco/Mistic/ebbce7bd66b26bc368f64e74555bd131e6d6cd5f/Mistic_code/code/image_tSNE_GUI/static/image_tsne_tils_all_3_rot.png -------------------------------------------------------------------------------- /Mistic_code/code/image_tSNE_GUI/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends base %} 2 | 3 | {% block title %}Bokeh Image t-SNE{% endblock %} 4 | 5 | {% block preamble %} 6 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /Mistic_code/code/mistic.sh: -------------------------------------------------------------------------------- 1 | find . | grep .git | xargs rm -rf 2 | find . -name ".DS_Store" -delete 3 | bokeh serve --port 5098 --show image_tSNE_GUI 4 | -------------------------------------------------------------------------------- /Mistic_code/code/output_tiles/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/figures/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/CODEX/Marker_ids.csv: -------------------------------------------------------------------------------- 1 | Keratin14 2 | FoxP3 3 | CD34 4 | CD8 5 | CD3e 6 | CD68 7 | Perlecan -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/CODEX/markers.csv: -------------------------------------------------------------------------------- 1 | cycle_number,channel_number,marker_name 2 | 1,1,DAPI-1 3 | 1,2,Blank-1 4 | 1,3,Blank-2 5 | 1,4,Blank-3 6 | 2,5,DAPI-2 7 | 2,6,Keratin19 8 | 2,7,Myosin 9 | 2,8,PCNA 10 | 3,9,DAPI-3 11 | 3,10,Vimentin 12 | 3,11,CD34 13 | 3,12,FoxA1 14 | 4,13,DAPI-4 15 | 4,14,Keratin17 16 | 4,15,S100A4 17 | 4,16,CollagenIV 18 | 5,17,DAPI-5 19 | 5,18,E-cadherin 20 | 5,19,CD31 21 | 5,20,FoxP3 22 | 6,21,DAPI-6 23 | 6,22,b-catenin1 24 | 6,23,Ki67 25 | 6,24,CD68 26 | 7,25,DAPI-7 27 | 7,26,Keratin8 28 | 7,27,Perlecan 29 | 7,28,PR 30 | 8,29,DAPI-8 31 | 8,30,b-actin 32 | 8,31,TFAM 33 | 8,32,CD3e 34 | 9,33,DAPI-9 35 | 9,34,Keratin18 36 | 9,35,E2F1 37 | 9,36,SMA 38 | 10,37,DAPI-10 39 | 10,38,MHCI 40 | 10,39,CD8 41 | 10,40,Runx3 42 | 11,41,DAPI-11 43 | 11,42,CD227 44 | 11,43,LaminB1 45 | 11,44,CD66e 46 | 12,45,DAPI-12 47 | 12,46,Ch2Cy12 48 | 12,47,MHCII 49 | 12,48,TP63 50 | 13,49,DAPI-13 51 | 13,50,Ch2Cy13 52 | 13,51,Ch3Cy13 53 | 13,52,H2A.X 54 | 14,53,DAPI-14 55 | 14,54,Ch2Cy14 56 | 14,55,Ch3Cy14 57 | 14,56,Keratin5 58 | 15,57,DAPI-15 59 | 15,58,Ch2Cy15 60 | 15,59,Keratin14 61 | 15,60,p53 62 | 16,61,DAPI-16 63 | 16,62,Blank-4 64 | 16,63,Blank-5 65 | 16,64,Blank-6 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/CyCIF/Marker_ids.csv: -------------------------------------------------------------------------------- 1 | Marker_1 2 | Marker_2 3 | Marker_3 4 | Marker_4 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/CyCIF/markers.csv: -------------------------------------------------------------------------------- 1 | cycle_number,channel_number,marker_name 2 | 1,1,Marker_1 3 | 1,2,Marker_2 4 | 1,3,Marker_3 5 | 1,4,Marker_4 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/Cluster_categories.csv: -------------------------------------------------------------------------------- 1 | 0 2 | 0 3 | 0 4 | 0 5 | 1 6 | 1 7 | 2 8 | 2 9 | 2 10 | 1 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/Marker_ids.csv: -------------------------------------------------------------------------------- 1 | Marker_1 2 | Marker_2 3 | Marker_3 4 | Marker_4 5 | Marker_5 6 | Marker_6 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/Patient_ids.csv: -------------------------------------------------------------------------------- 1 | 4 2 | 4 3 | 4 4 | 5 5 | 3 6 | 2 7 | 0 8 | 0 9 | 1 10 | 3 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/Response_categories.csv: -------------------------------------------------------------------------------- 1 | Response 1 2 | Response 1 3 | Response 1 4 | Response 1 5 | Response 2 6 | Response 2 7 | Response 2 8 | Response 2 9 | Response 2 10 | Response 2 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/Treatment_categories.csv: -------------------------------------------------------------------------------- 1 | Treatment 1 2 | Treatment 2 3 | Treatment 2 4 | Treatment 2 5 | Treatment 1 6 | Treatment 2 7 | Treatment 1 8 | Treatment 1 9 | Treatment 1 10 | Treatment 1 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/X_imagetSNE.csv: -------------------------------------------------------------------------------- 1 | 1.3549204,-4.9493012 2 | 2.9607117,-7.3730865 3 | 3.5662005,-3.4312763 4 | 2.268297,-2.2885783 5 | 5.6273146,0.73978335 6 | 3.9662726,2.4958441 7 | -2.5741525,0.28250438 8 | -4.058194,3.2042348 9 | -2.2520936,1.395093 10 | 2.312218,0.39663085 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/Vectra/markers.csv: -------------------------------------------------------------------------------- 1 | cycle_number,channel_number,marker_name 2 | 1,1,Marker_1 3 | 1,2,Marker_2 4 | 1,3,Marker_3 5 | 1,4,Marker_4 6 | 2,5,Marker_5 7 | 2,6,Marker_6 -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/t-CyCIF/Marker_ids.csv: -------------------------------------------------------------------------------- 1 | KERATIN 2 | FOXP3 3 | CD45 4 | PD-1 5 | PD-L1 6 | KI67 7 | CD8A -------------------------------------------------------------------------------- /Mistic_code/code/user_inputs/metadata/t-CyCIF/markers.csv: -------------------------------------------------------------------------------- 1 | cycle_number,channel_number,marker_name 2 | 1,1,DNA1 3 | 1,2,488_BG_1 4 | 1,3,555_BG_1 5 | 1,4,647_BG_1 6 | 2,5,DNA2 7 | 2,6,488_BG_2 8 | 2,7,555_BG_2 9 | 2,8,647_BG_2 10 | 3,9,DNA3 11 | 3,10,488_BG_3 12 | 3,11,LAG3 13 | 3,12,ARL13B 14 | 4,13,DNA4 15 | 4,14,KI67 16 | 4,15,KERATIN 17 | 4,16,PD-1 18 | 5,17,DNA5 19 | 5,18,CD45RB 20 | 5,19,CD3D 21 | 5,20,PD-L1 22 | 6,21,DNA6 23 | 6,22,CD4 24 | 6,23,CD45 25 | 6,24,CD8A 26 | 7,25,DNA7 27 | 7,26,CD163 28 | 7,27,CD68 29 | 7,28,CD14 30 | 8,29,DNA8 31 | 8,30,CD11B 32 | 8,31,FOXP3 33 | 8,32,CD21 34 | 9,33,DNA9 35 | 9,34,IBA1 36 | 9,35,ASMA 37 | 9,36,CD20 38 | 10,37,DNA10 39 | 10,38,CD19 40 | 10,39,GFAP 41 | 10,40,G-TUBULIN 42 | 11,41,DNA11 43 | 11,42,LAMIN_A/C 44 | 11,43,BANF1 45 | 11,44,LAMIN_B 46 | -------------------------------------------------------------------------------- /Mistic_code/color_scatter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | color_scatter.py example 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 44 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Mistic_code/image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | image.py example 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 47 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Mistic_code/theme.yaml: -------------------------------------------------------------------------------- 1 | attrs: 2 | Figure: 3 | background_fill_color: '#938dba' 4 | border_fill_color: '#2F2F2F' 5 | outline_line_color: '#444444' 6 | Axis: 7 | axis_line_color: "white" 8 | axis_label_text_color: "white" 9 | major_label_text_color: "white" 10 | major_tick_line_color: "white" 11 | minor_tick_line_color: "white" 12 | minor_tick_line_color: "white" 13 | Grid: 14 | grid_line_dash: [6, 4] 15 | grid_line_alpha: .3 16 | Title: 17 | text_color: "white" 18 | -------------------------------------------------------------------------------- /Mistic_code/title.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Bokeh Plot 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 46 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Mistic: image tSNE visualizer 2 | 3 | This is a Python tool using the Bokeh library to view multiple multiplex images simultaneously. The code has been tested on 7-panel Vectra TIFF, 32- & 64-panel CODEX TIFF, 16-panel CODEX QPTIFF, 4-panel CyCIF TIFF, and 44-panel t-CyCIF TIFF images. 4 | 5 | Mistic is published at [Patterns (2022)](https://www.cell.com/patterns/fulltext/S2666-3899(22)00120-9). 6 | 7 | Mistic's GUI with user inputs is shown below: 8 | 9 | 10 | 11 | Figure description: A sample Mistic GUI with user inputs is shown. **A.** User-input panel where imaging technique choice, stack montage option or markers can be selected, images borders can be added, new or pre-defined image display coordinates can be chosen, and a theme for the canvases can be selected. **B.** Static canvas showing the image t-SNE colored and arranged as per user inputs. **C.** Live canvas showing the corresponding t-SNE scatter plot where each image is represented as a dot. The live canvas has tabs for displaying additional information per image. Metadata for each image can be obtained by hovering over each dot. 12 | 13 | 14 | ## Features of Mistic 15 | * Two canvases: 16 | * still canvas with the image tSNE rendering 17 | * live canvases with tSNE scatter plots for image metadata rendering 18 | * Dropdown option to select the imaging technique: Vectra, CyCIF, t-CyCIF, or CODEX 19 | * Option to choose between Stack montage view or multiple multiplexed images by selecting the markers to be visualised at once 20 | * Option to place a border around each image based on image metadata 21 | * Option to use a pre-defined tSNE or generate a new set of tSNE co-ordinates 22 | * Option to shuffle images with the tSNE co-ordinates 23 | * Option to render multiple tSNE scatter plots based on image metadata 24 | * Hover functionality available on the tSNE scatter plot to get more information of each image 25 | * Save, zoom, etc each of the Bokeh canvases 26 | 27 | ## Requirements 28 | 29 | * Python >= 3.6 30 | * Install Python from here: https://www.python.org/downloads/ 31 | 32 | 33 | 34 | 35 | * Open a command prompt (or the Terminal application): 36 | * Download ``` pip ```. Type: 37 | * ``` curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py ``` 38 | * ``` python3 get-pip.py ``` and wait for the installation to complete 39 | * Verify the ``` pip ``` installation by typing ``` pip --version ``` 40 | * ```pip install mistic ``` 41 | 42 | ## Setting up Mistic 43 | 44 | * Download this code repository or Open Terminal and use `git clone` 45 | 46 | `$ git clone https://github.com/MathOnco/Mistic.git` 47 | 48 | * In the Mistic folder, navigate to /user_inputs folder to upload input files: 49 | * ```Mistic_code/code/user_inputs/``` 50 | * Use the /figures folder to upload the multiplexed images 51 | * Example NSCLC Vectra dataset is available at: https://doi.org/10.5281/zenodo.6131933 52 | * Use the /metadata folder to 53 | * Upload the imaging markers of interest as Markers_ids.csv and markers.csv. 54 | * Example files are provided in the subfolders: Vectra, CyCIF, t-CyCIF and CODEX 55 | * Move the files from the relevant subfolder into the /metadata folder 56 | * Note: For the Stack Montage option, only the markers.csv file is required 57 | * Optional uploads: 58 | * Upload image tSNE co-ordinates as X_imagetSNE.csv 59 | * If no user-generated tSNE co-ordinates are provided, Mistic will generate a set of t-SNE coordinates to render the images 60 | * Upload image metadata such as 61 | * Cluster labels as Cluster_categories.csv 62 | * If cluster labels are not provided, Mistic will cluster the images using a Bayesian mixture model. 63 | * Patient_ids as Patient_ids.csv 64 | * Treatments as Treatment_catgories.csv 65 | * Patient response as Response_categories.csv 66 | * If any of these are unavailable, Mistic will use either the randomly-generated or user-provided tSNE points without any color coding i.e. dots are colored in gray. 67 | * Sample metadata files are provided for reference in separate subfolders for each imaging technique (Vectra, CyCIF, t-CyCIF and CODEX) in the /metadata folder 68 | * If using the sample metadata, move the files from the relevant subfolder into the /metadata folder 69 | 70 | ## Run Mistic 71 | 72 | * Open a command prompt (or the Terminal application), change to the directory containing /code and type: 73 | * ```bash mistic.sh``` 74 | * This runs a bokeh server locally and will automatically open the interactive dashboard in your browser at http://localhost:5098/image_tSNE_GUI 75 | * Enter the imaging format, montage or multiplexed views and other user options on the GUI and click ```Run```. 76 | 77 | * Examples for running Mistic: 78 | * For instructions on how to run Mistic on the t-CyCIF data, please check: https://mistic-rtd.readthedocs.io/en/latest/vignette_example_tcycif.html 79 | 80 | * For instructions on how to run Mistic on the toy data from our NSCLC Vectra FoVs, please check:https://mistic-rtd.readthedocs.io/en/latest/vignette_example_vectra.html 81 | 82 | 83 | * If you get an error: ```Cannot start Bokeh server, port 5098 is already in use```, then at the Terminal, issue: 84 | * ```ps -ef | grep 5098``` 85 | * You should see a line similar to the one below on the Terminal: 86 | ```55525 12519 11678 0 1:22AM ttys004 0:57.81 /opt/anaconda3/bin/python /opt/anaconda3/bin/bokeh serve --port 5098 --show image_tSNE_GUI``` 87 | where the 2nd term is the _process id_. Here this is '12519'. 88 | * Use this _process id_ to kill the process: ```kill -9 12519``` 89 | 90 | 91 | ## Additional information 92 | 93 | * Paper on bioRxiv: https://www.biorxiv.org/content/10.1101/2021.10.08.463728v1 94 | * Documentation: https://mistic-rtd.readthedocs.io 95 | * Code has been published at Zenodo: https://doi.org/10.5281/zenodo.5912169 96 | * Example NSCLC Vectra images are published here: https://doi.org/10.5281/zenodo.6131933 97 | * Mistic is highlighted on Bokeh's user showcase: http://bokeh.org/ 98 | 99 | 100 | -------------------------------------------------------------------------------- /fig_readme/Figure_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MathOnco/Mistic/ebbce7bd66b26bc368f64e74555bd131e6d6cd5f/fig_readme/Figure_2.jpg -------------------------------------------------------------------------------- /fig_readme/GUI_Mistic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MathOnco/Mistic/ebbce7bd66b26bc368f64e74555bd131e6d6cd5f/fig_readme/GUI_Mistic.png --------------------------------------------------------------------------------