Skip to content
This repository was archived by the owner on Oct 15, 2025. It is now read-only.

Commit fcda860

Browse files
author
JacksonMaxfield
committed
label free ds changes
1 parent 85af961 commit fcda860

11 files changed

+339
-167
lines changed

_posts/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers.md

Lines changed: 113 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ A category of convolutional neural-net models, those based on the [U-net archite
3333

3434
We've trained such models on paired sets of our fluorescent images and the corresponding brightfield images. The details of our architecture and approach are described [in more detail in this pre-print](https://www.biorxiv.org/content/early/2018/03/27/289504), but let's take a look at how well structure prediction works, how close we are to being able to acquire fluorescence-like images without fluorescent markers.
3535

36-
## Predicting fluorescence images from bright-field images
36+
## Predicting Fluorescence Images from Bright-field Images
3737

38-
### Getting support packages, the model, and input images
38+
### Getting Support Packages, the Model, and Input Images
3939

4040
This notebook runs well on [Google's Colaboratory Environment](https://drive.google.com/file/d/1aXtGzmqXxKTrraVZu0eg7JBf9Zt4iFFE/view?usp=sharing), which gives you access to nice GPUs from your laptop. We've also created a version to run this model across larger sets of images that allows you to [upload your own datasets](https://drive.google.com/file/d/1gIBqWhMCRxfX_NZgy_rAywEMh8QrpHQZ/view?usp=sharing). Before we begin in this environment however, we will need to install some packages and get local copies of the trained model and some input images to test it on.
4141

@@ -143,7 +143,7 @@ dna_model = fnet.fnet_model.Model()
143143
dna_model.load_state(dna_model_fn, gpu_ids=0)
144144
```
145145

146-
### Predicting fluorescent images from brightfield images
146+
### Predicting Fluorescent Images from Brightfield Images
147147

148148
Now that our model is loaded, let's prepare the previously loaded image for use with our model.
149149

@@ -173,30 +173,50 @@ To compare the observed DNA fluorescent dye image with our predicted DNA fluores
173173

174174

175175
```python
176-
# Two compare the two images, prep the original
176+
# To compare the two images, prep the original
177177
full_dna = img[:, channels['dna'],:,:]
178178
small_dna = fnet.transforms.prep_ndarray(full_dna, dna_opts['transform_signal'])
179179
```
180180

181+
Let's also prepare the images for display by normalizing and clipping outliers.
182+
181183

182184
```python
183-
# Display the observed and the predicted
184-
fig, axes = plt.subplots(1, 2, figsize=(14,6))
185+
# Find the observed and predicted outlier cutoff points
186+
target_flat = small_dna.copy().flatten()
187+
min_px = np.percentile(target_flat, 0.02)
188+
max_px = np.percentile(target_flat, 99.8)
189+
190+
# normalize and clip
191+
def prep_image_for_display(img, min_px, max_px, clip_lower=0, clip_upper=1):
192+
img = img.copy() - min_px
193+
img /= (max_px - min_px)
194+
img[img < clip_lower] = clip_lower
195+
img[img > clip_upper] = clip_upper
196+
return img
197+
198+
display_obs = prep_image_for_display(small_dna, min_px, max_px)
199+
display_pred = prep_image_for_display(predicted_dna, min_px, max_px)
200+
```
185201

186-
axes[0].imshow(small_dna.max(0))
187-
axes[0].set_title('observed')
188-
axes[0].axis('off')
189202

190-
axes[1].imshow(predicted_dna.max(0))
191-
axes[1].set_title('predicted')
192-
axes[1].axis('off');
203+
```python
204+
imgs = {'Observed': display_obs,
205+
'Predicted': display_pred}
206+
207+
# Display the observed and the predicted
208+
fig, axes = plt.subplots(1, len(imgs), figsize=(14,6))
209+
for label, ax in zip(imgs.keys(), axes.flat):
210+
ax.imshow(imgs[label].max(0))
211+
ax.set_title(label)
212+
ax.axis('off')
193213
```
194214

195215

196-
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_19_0.png)
216+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_21_0.png)
197217

198218

199-
### Visualizing the difference between the observed and predicted images
219+
### Visualizing the Difference
200220

201221
We'd like a better sense of how our prediction differs from the observed image than we can get from the above two images at a glance. Let's compare them in a couple of other ways. The simplest comparison is simply to toggle back and forth between the two images. This lets your eye pick out differences that aren't apparent when you have to move from one image to the next.
202222

@@ -208,7 +228,7 @@ This may be helpful in finding areas where our prediction is noticeably off.
208228
from IPython.display import Image
209229
import imageio
210230

211-
ordered_imgs = [small_dna.max(0), predicted_dna.max(0)]
231+
ordered_imgs = [display_obs.max(0), display_pred.max(0)]
212232
imageio.mimsave('compare.gif', ordered_imgs, duration=2)
213233
```
214234

@@ -219,85 +239,113 @@ with open('compare.gif', 'rb') as f:
219239
```
220240

221241

222-
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_22_0.png)
242+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_24_0.png)
223243

224244

225-
To my eye this shows that our predictions are largely accurate in location, with some underprediction of the extent of the DNA dye uptake by the mitotic cell. Also of interest is that our predicted image is a bit smoother or less grainy than is the observed image. We can put this down to stochastic uptake of dye or dye clumping that the model is unable to predict.
245+
To my eye this shows that our predictions are largely accurate in location, with some underprediction of the extent of the DNA dye uptake by the mitotic cell. Also of interest is that our predicted image is a bit smoother or less grainy than the observed image. We can put this down to stochastic uptake of dye or dye clumping that the model is unable to predict.
226246

227-
What is the absolute difference between our observed image and our predicted image? Let's take a look:
247+
But how else can we visualize the location differences? Let's take a look:
228248

229249

230250
```python
231-
# Absolute difference
232-
dna_diff = abs(small_dna - predicted_dna)
233-
234-
# Display the observed DNA, the predicted DNA, and their absolute difference
235-
images = {'Observed': small_dna,
236-
'Difference': dna_diff,
237-
'Predicted': predicted_dna}
238-
239-
# Normalize the greyscale intensities by finding the max/min intensity of all
240-
max_gs = max([i.max(0).max(0).max(0) for l, i in images.items()])
241-
min_gs = min([i.min(0).min(0).min(0) for l, i in images.items()])
242-
243-
# Display the images with appropriate normalization
244-
fig, axes = plt.subplots(1, len(images), figsize=(18, 6))
245-
for label, ax in zip(images.keys(), axes):
246-
ax.imshow(images[label].max(0), vmax=max_gs, vmin=min_gs)
251+
imgs = {'Observed - Predicted': small_dna - predicted_dna,
252+
'Predicted - Observed': predicted_dna - small_dna,
253+
'Absolute Difference': abs(small_dna - predicted_dna)}
254+
255+
# Find the difference images outlier cut off points
256+
unified_flat = np.append(np.empty(0,), [imgs[l] for l in imgs]).flatten()
257+
min_px = np.percentile(unified_flat, 0.02)
258+
max_px = np.percentile(unified_flat, 99.8)
259+
260+
fig, axes = plt.subplots(1, len(imgs), figsize=(14,10))
261+
for label, ax in zip(imgs.keys(), axes.flat):
262+
display_img = prep_image_for_display(imgs[label], min_px, max_px)
263+
ax.imshow(display_img.mean(0))
247264
ax.set_title(label)
248265
ax.axis('off')
266+
267+
plt.tight_layout()
249268
```
250269

251270

252-
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_24_0.png)
271+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_26_0.png)
272+
253273

274+
**Observed - Predicted** provides us information on where we underpredict, and you can instantly see that we underpredict the mitotic the most out of all other cells present in the image.
254275

255-
An ideal prediction would have a difference image that is completely empty (black).
276+
**Predicted - Observed** gives context as to where we overpredict. Noticable here is that it looks as though we generally add noise to the image, even though in the displayed predicted image it looked as though we didn't. This is due to how the model was trained. Because the model cannot predict the random noise generated by fluorescence microscopy, it instead averages the spots where it thinks noise would be present.
256277

257-
While our difference image indicates that we didn't predict exactly the same image as the observed image, this image also gives us more confidence that we are able to predict location reasonably well at first pass. Notably, visible error looks like it correleates with areas where the observed image has signal. This means we aren't getting the magnitude quite right, but we *are* getting the location of the fluorescence right.
278+
**Absolute Difference** shows us all the spots in a unified image of where our prediction was incorrect when compared to the observed. With largest difference being at the mitotic of course and all other points looking like they are mainly differences in greyscale intensity between the prediction and observed and not too many cell location differences.
258279

280+
## Uses of Our Predictions
259281

282+
In the small examples above we showed the relative location accuracy of our predictions and if you would like to learn more about the overall accuracy of our predictions we encourage you to read our paper. But what value can we gain from these predictions. We will first look at segmentation followed by predicting structures not tagged.
260283

261-
That emphasizes the relative accuracy in location of the fluorescent marker. But what about the intensity error? It is hard to get a sense of magnitude from a colorscale. To build intuition about error magnitude, let's look at the observed image, the difference, and the predicted image side-by-side with pixel intensity mapped to height in a surface plot.
284+
### Segmentation and Thresholding
285+
286+
Segmentation is a large part of our pipeline so let's see how the observed fluorescence image compare to the predicted under a threshold.
262287

263288

264289
```python
265-
from mpl_toolkits.mplot3d import Axes3D
266-
import matplotlib.pyplot as plt
290+
from skimage.filters import threshold_otsu
291+
292+
thresh_obs = threshold_otsu(small_dna)
293+
binary_obs = small_dna > thresh_obs
294+
thresh_pred = threshold_otsu(predicted_dna)
295+
binary_pred = predicted_dna > thresh_pred
296+
297+
imgs = {'Observed': display_obs,
298+
'Predicted': display_pred,
299+
'Binary Observed': binary_obs,
300+
'Binary Predicted': binary_pred}
267301

268-
# Update the max greyscale by first taking the sum across z
269-
max_gs = max([i.sum(0).max(0).max(0) for l, i in images.items()])
302+
fig, axes = plt.subplots(2, int(len(imgs) / 2), figsize=(14,10))
303+
for label, ax in zip(imgs.keys(), axes.flat):
304+
ax.imshow(imgs[label].max(0))
305+
ax.set_title(label)
306+
ax.axis('off')
270307

271-
# Plot each image by creating a mesh and setting the new z axis limits
272-
fig, axes = plt.subplots(1, len(images), figsize=(18,6), subplot_kw={'projection': '3d'})
273-
for label, ax in zip(images.keys(), axes):
274-
x = np.arange(images[label].shape[2])
275-
y = np.arange(images[label].shape[1])
276-
x, y = np.meshgrid(y, x)
308+
plt.tight_layout()
309+
```
277310

278-
mag_sum = np.sum(images[label], axis=0)
279-
mag_sum = np.rollaxis(mag_sum, 1, 0)
280-
mag_sum = scipy.ndimage.gaussian_filter(mag_sum, 1) #else single pixel error predominates
281311

282-
ax.plot_surface(x, y, mag_sum, rstride=1, cstride=1, cmap=plt.cm.gray, linewidth=1)
283-
284-
ax.set_zlim([0, max_gs])
285-
ax.set_title(label, fontsize=20, linespacing=3)
286-
ax.set_xlabel('\nY', fontsize=20, linespacing=1.1)
287-
ax.set_ylabel('\nX', fontsize=20, linespacing=3)
288-
ax.set_zlabel('\n\n\nSum of\nGreyscale Intensity', fontsize=20)
289-
ax.xaxis.pane.set_edgecolor('black')
290-
ax.yaxis.pane.set_edgecolor('black')
291-
ax.set_facecolor((1.0, 1.0, 1.0, 1.0))
312+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_30_0.png)
313+
314+
315+
It was pretty intuitive that our predictions were generally 'smoother' and had less noise in the observed fluorescence but the thresholding shows how much noise was present in the original that is filtered out on the thresholded prediction.
316+
317+
While we can continue on using the predicted image as a segmentation input itself we could also use the binary predicted image as a mask for the original to view a cleaned up, or de-noised original image. In short, let's use the binary predicted as a clipping mask for the original observed fluorescence image.
318+
319+
320+
```python
321+
# Use binary predicted as a clipping mask
322+
clipped_obs = small_dna.copy()
323+
clipped_flat = small_dna.flatten()
324+
clipped_obs[binary_pred==False] = min(clipped_flat)
325+
clipped_flat = clipped_obs.flatten()
326+
min_px = np.percentile(clipped_flat, 0.02)
327+
max_px = np.percentile(clipped_flat, 99.8)
328+
clipped_obs = prep_image_for_display(clipped_obs, min_px, max_px)
329+
330+
imgs = {'Original Observed': display_obs,
331+
'Clipped Observed': clipped_obs}
332+
333+
fig, axes = plt.subplots(1, len(imgs), figsize=(14,10))
334+
for label, ax in zip(imgs.keys(), axes.flat):
335+
ax.imshow(imgs[label].max(0))
336+
ax.set_title(label)
337+
ax.axis('off')
338+
339+
plt.tight_layout()
292340
```
293341

294342

295-
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_27_0.png)
343+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_32_0.png)
296344

297345

298-
The middle surface plot would be completely flat for an ideal model. Note that the largest difference in intensity is at the mitotic cell, which is also where we have the largest intensity in the observed image. One of the other interesting things that pops out of this is the 'noise' overlaid on the error. The model is unable to predict what is likely detector noise.
346+
That looks much better! Being able to filter out some of the noise while still using the original observed fluorescence will be valuable in generating better segmentations and performing feature analysis.
299347

300-
### Predicting structures not tagged
348+
### Predicting Structures not Tagged
301349

302350
These error plots give us some confidence in our ability to predict where DNA fluorescence will appear. We can plot simmilarly estimate error for our other tag models, but this is left as an exercise for the reader. Hard values for how well each of these models work are found in [the accompanying paper](https://www.biorxiv.org/content/early/2018/03/28/289504).
303351

@@ -351,7 +399,7 @@ for img_name, ax in zip(predictions.keys(), axes.flat):
351399
```
352400

353401

354-
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_32_0.png)
402+
![png](../assets/nbfiles/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers/2018-05-03-Fluorescence_Images_without_Fluorescent_Markers_37_0.png)
355403

356404

357405
What a nice way to get information from a brightfield image using patterns of structures we've seen in other images! We can see fluorescent structures in cells where we haven't even labeled them. In cases where we can do a little initial dying or labeling to train models of this type, this can drastically improve the interpretability of large sets of brightfield images.
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)