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

Commit a2fca9d

Browse files
authored
Add heatmap (#6)
* update imlib api call * add heatmap * heatmap test
1 parent 6af2057 commit a2fca9d

File tree

277 files changed

+73589
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

277 files changed

+73589
-1
lines changed

neuro/heatmap/__init__.py

Whitespace-only changes.

neuro/heatmap/heatmap.py

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
"""
2+
Based on cellfinder detected cells, and amap registration, generate a heatmap
3+
image
4+
"""
5+
6+
import logging
7+
import argparse
8+
9+
import numpy as np
10+
from skimage.filters import gaussian
11+
from skimage.transform import resize
12+
13+
from brainio import brainio
14+
from imlib.cells.utils import get_cell_location_array
15+
from imlib.image.scale import scale_and_convert_to_16_bits
16+
from imlib.image.binning import get_bins
17+
from imlib.image.shape import convert_shape_dict_to_array_shape
18+
from imlib.image.masking import mask_image_threshold
19+
from imlib.general.misc import check_positive_float
20+
21+
22+
def run(
23+
cells_file,
24+
output_filename,
25+
target_size,
26+
raw_image_shape,
27+
raw_image_bin_sizes,
28+
transformation_matrix,
29+
atlas_scale,
30+
smoothing=10,
31+
mask=True,
32+
atlas=None,
33+
cells_only=True,
34+
convert_16bit=True,
35+
):
36+
"""
37+
38+
:param cells_file: Cellfinder output cells file.
39+
:param output_filename: File to save heatmap into
40+
:param target_size: Size of the final heatmap
41+
:param raw_image_shape: Size of the raw data (coordinate space of the
42+
cells)
43+
:param raw_image_bin_sizes: List/tuple of the sizes of the bins in the
44+
raw data space
45+
:param transformation_matrix: Transformation matrix so that the resulting
46+
nifti can be processed using other tools.
47+
:param atlas_scale: Image scaling so that the resulting nifti can be
48+
processed using other tools.
49+
:param smoothing: Smoothing kernel size, in the target image space
50+
:param mask: Whether or not to mask the heatmap based on an atlas file
51+
:param atlas: Atlas file to mask the heatmap
52+
:param cells_only: Only use "cells", not artefacts
53+
:param convert_16bit: Convert final image to 16 bit
54+
55+
56+
"""
57+
58+
# TODO: compare the smoothing effects of gaussian filtering, and upsampling
59+
target_size = convert_shape_dict_to_array_shape(target_size, type="fiji")
60+
raw_image_shape = convert_shape_dict_to_array_shape(
61+
raw_image_shape, type="fiji"
62+
)
63+
cells_array = get_cell_location_array(cells_file, cells_only=cells_only)
64+
bins = get_bins(raw_image_shape, raw_image_bin_sizes)
65+
66+
logging.debug("Generating heatmap (3D histogram)")
67+
heatmap_array, _ = np.histogramdd(cells_array, bins=bins)
68+
69+
logging.debug("Resizing heatmap to the size of the target image")
70+
heatmap_array = resize(heatmap_array, target_size, order=0)
71+
if smoothing is not None:
72+
logging.debug(
73+
"Applying Gaussian smoothing with a kernel sigma of: "
74+
"{}".format(smoothing)
75+
)
76+
heatmap_array = gaussian(heatmap_array, sigma=smoothing)
77+
78+
if mask:
79+
logging.debug("Masking image based on registered atlas")
80+
# copy, otherwise it's modified, which affects later figure generation
81+
atlas_for_mask = np.copy(atlas)
82+
heatmap_array = mask_image_threshold(heatmap_array, atlas_for_mask)
83+
84+
if convert_16bit:
85+
logging.debug("Converting to 16 bit")
86+
heatmap_array = scale_and_convert_to_16_bits(heatmap_array)
87+
88+
logging.debug("Saving heatmap image")
89+
brainio.to_nii(
90+
heatmap_array,
91+
output_filename,
92+
scale=atlas_scale,
93+
affine_transform=transformation_matrix,
94+
)
95+
96+
97+
def get_parser():
98+
parser = argparse.ArgumentParser(
99+
formatter_class=argparse.ArgumentDefaultsHelpFormatter
100+
)
101+
parser.add_argument(
102+
dest="cells_file", type=str, help="Cellfinder output cell file",
103+
)
104+
parser.add_argument(
105+
dest="output_filename",
106+
type=str,
107+
help="Output filename. Should end with '.nii'",
108+
)
109+
110+
parser.add_argument(
111+
dest="raw_image", type=str, help="Paths to raw data",
112+
)
113+
114+
parser.add_argument(
115+
dest="downsampled_image", type=str, help="Downsampled_atlas .nii file",
116+
)
117+
parser.add_argument(
118+
"--bin-size",
119+
dest="bin_size_um",
120+
type=check_positive_float,
121+
default=100,
122+
help="Heatmap bin size (um of each edge of histogram cube)",
123+
)
124+
parser.add_argument(
125+
"-x",
126+
"--x-pixel-um",
127+
dest="x_pixel_um",
128+
type=check_positive_float,
129+
help="Pixel spacing of the data in the first "
130+
"dimension, specified in um.",
131+
)
132+
parser.add_argument(
133+
"-y",
134+
"--y-pixel-um",
135+
dest="y_pixel_um",
136+
type=check_positive_float,
137+
help="Pixel spacing of the data in the second "
138+
"dimension, specified in um.",
139+
)
140+
parser.add_argument(
141+
"-z",
142+
"--z-pixel-um",
143+
dest="z_pixel_um",
144+
type=check_positive_float,
145+
help="Pixel spacing of the data in the third "
146+
"dimension, specified in um.",
147+
)
148+
parser.add_argument(
149+
"--heatmap-smoothing",
150+
dest="heatmap_smooth",
151+
type=check_positive_float,
152+
default=100,
153+
help="Gaussian smoothing sigma, in um.",
154+
)
155+
parser.add_argument(
156+
"--no-mask-figs",
157+
dest="mask_figures",
158+
action="store_false",
159+
help="Don't mask the figures (removing any areas outside the brain,"
160+
"from e.g. smoothing)",
161+
)
162+
163+
return parser
164+
165+
166+
class HeatmapParams:
167+
# assumes an isotropic target space
168+
def __init__(
169+
self,
170+
raw_image,
171+
downsampled_image,
172+
bin_size_um,
173+
x_pixel_um,
174+
y_pixel_um,
175+
z_pixel_um,
176+
smoothing_target_space,
177+
):
178+
self._input_image = raw_image
179+
self._target_image = downsampled_image
180+
self._bin_um = bin_size_um
181+
self._x_pixel_um = x_pixel_um
182+
self._y_pixel_um = y_pixel_um
183+
self._z_pixel_um = z_pixel_um
184+
self._smooth_um = smoothing_target_space
185+
self._downsampled_image = None
186+
187+
self.figure_image_shape = None
188+
self.raw_image_shape = None
189+
self.bin_size_raw_voxels = None
190+
self.atlas_scale = None
191+
self.transformation_matrix = None
192+
self.smoothing_target_voxel = None
193+
194+
self._get_raw_image_shape()
195+
self._get_figure_image_shape()
196+
self._get_atlas_data()
197+
self._get_atlas_scale()
198+
self._get_transformation_matrix()
199+
self._get_binning()
200+
self._get_smoothing()
201+
202+
def _get_raw_image_shape(self):
203+
logging.debug("Checking raw image size")
204+
self.raw_image_shape = brainio.get_size_image_from_file_paths(
205+
self._input_image
206+
)
207+
logging.debug(f"Raw image size: {self.raw_image_shape}")
208+
209+
def _get_figure_image_shape(self):
210+
logging.debug(
211+
"Loading file: {} to check target image size"
212+
"".format(self._target_image)
213+
)
214+
self._downsampled_image = brainio.load_nii(
215+
self._target_image, as_array=False
216+
)
217+
shape = self._downsampled_image.shape
218+
self.figure_image_shape = {"x": shape[0], "y": shape[1], "z": shape[2]}
219+
logging.debug("Target image size: {}".format(self.figure_image_shape))
220+
221+
def _get_binning(self):
222+
logging.debug("Calculating bin size in raw image space voxels")
223+
bin_raw_x = int(self._bin_um / self._x_pixel_um)
224+
bin_raw_y = int(self._bin_um / self._y_pixel_um)
225+
bin_raw_z = int(self._bin_um / self._z_pixel_um)
226+
self.bin_size_raw_voxels = [bin_raw_x, bin_raw_y, bin_raw_z]
227+
logging.debug(
228+
f"Bin size in raw image space is x:{bin_raw_x}, "
229+
f"y:{bin_raw_y}, z:{bin_raw_z}."
230+
)
231+
232+
def _get_atlas_data(self):
233+
self.atlas_data = self._downsampled_image.get_fdata()
234+
235+
def _get_atlas_scale(self):
236+
self.atlas_scale = self._downsampled_image.header.get_zooms()
237+
238+
def _get_transformation_matrix(self):
239+
self.transformation_matrix = self._downsampled_image.affine
240+
241+
def _get_smoothing(self):
242+
logging.debug(
243+
"Calculating smoothing in target image volume. Assumes "
244+
"an isotropic target image"
245+
)
246+
if self._smooth_um is not 0:
247+
# 1000 is to scale to um
248+
self.smoothing_target_voxel = int(
249+
self._smooth_um / (self.atlas_scale[0] * 1000)
250+
)
251+
252+
253+
def main(
254+
cells_file,
255+
output_filename,
256+
raw_image,
257+
downsampled_image,
258+
bin_size_um,
259+
x_pixel_um,
260+
y_pixel_um,
261+
z_pixel_um,
262+
heatmap_smooth,
263+
masking,
264+
):
265+
params = HeatmapParams(
266+
raw_image,
267+
downsampled_image,
268+
bin_size_um,
269+
x_pixel_um,
270+
y_pixel_um,
271+
z_pixel_um,
272+
heatmap_smooth,
273+
)
274+
275+
run(
276+
cells_file,
277+
output_filename,
278+
params.figure_image_shape,
279+
params.raw_image_shape,
280+
params.bin_size_raw_voxels,
281+
params.transformation_matrix,
282+
params.atlas_scale,
283+
smoothing=params.smoothing_target_voxel,
284+
mask=masking,
285+
atlas=params.atlas_data,
286+
)
287+
288+
289+
def cli():
290+
args = get_parser().parse_args()
291+
main(
292+
args.cells_file,
293+
args.output_filename,
294+
args.raw_image,
295+
args.downsampled_image,
296+
args.bin_size_um,
297+
args.x_pixel_um,
298+
args.y_pixel_um,
299+
args.z_pixel_um,
300+
args.heatmap_smooth,
301+
args.mask_figures,
302+
)
303+
304+
305+
if __name__ == "__main__":
306+
cli()

setup.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from setuptools import setup, find_namespace_packages
22

3-
requirements = ["brainrender", "napari", "imlib", "pandas"]
3+
requirements = [
4+
"numpy",
5+
"scikit-image",
6+
"pandas",
7+
"napari",
8+
"brainrender",
9+
"imlib",
10+
"brainio",
11+
]
412

513

614
setup(
@@ -40,6 +48,7 @@
4048
"console_scripts": [
4149
"points_to_brainrender = "
4250
"neuro.points.points_to_brainrender:main",
51+
"heatmap = neuro.heatmap.heatmap:cli",
4352
]
4453
},
4554
zip_safe=False,

0 commit comments

Comments
 (0)