Skip to content

Commit d6232b4

Browse files
authored
Merge pull request #43 from ChengLabResearch/main
Merge in Color Channel changes to intermediates branch.
2 parents 49132da + 7247a3a commit d6232b4

File tree

5 files changed

+31
-23
lines changed

5 files changed

+31
-23
lines changed

.coverage

4 KB
Binary file not shown.

python/ouroboros/helpers/files.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,16 @@ def load_z_intermediate(path: Path, offset: int = 0):
194194

195195
def increment_volume(path: Path, vol: np.ndarray, offset: int = 0, cleanup=False):
196196
indicies, values, weights = load_z_intermediate(path, offset)
197-
np.add.at(vol[0], indicies, values)
198-
np.add.at(vol[1], indicies, weights)
197+
for i in range(0, vol.shape[0] - 1):
198+
np.add.at(vol[i], indicies, np.atleast_2d(values)[i])
199+
np.add.at(vol[-1], indicies, weights)
199200

200201
if cleanup:
201202
path.unlink()
202203

203204

204205
def volume_from_intermediates(path: Path, shape: DataShape, thread_count: int = 4):
205-
vol = np.zeros((2, np.prod((shape.Y, shape.X))), dtype=np.float32)
206+
vol = np.zeros((1 + shape.C, np.prod((shape.Y, shape.X))), dtype=np.float32)
206207
with ThreadPool(thread_count) as pool:
207208
if not path.exists():
208209
# We don't have any intermediate(s) for this value, so return empty.
@@ -214,9 +215,9 @@ def volume_from_intermediates(path: Path, shape: DataShape, thread_count: int =
214215
offset_set = range(0, len(tif.series), 4)
215216
pool.starmap(increment_volume, [(path, vol, i, False) for i in offset_set])
216217

217-
nz = np.flatnonzero(vol[0])
218-
vol[0, nz] /= vol[1, nz]
219-
return vol[0]
218+
nz = np.flatnonzero(vol[-1])
219+
vol[:-1, nz] /= vol[-1, nz]
220+
return vol[:-1]
220221

221222

222223
def write_conv_vol(writer: callable, source_path, shape, dtype, scaling, target_folder, index, interpolation):
@@ -228,7 +229,7 @@ def write_conv_vol(writer: callable, source_path, shape, dtype, scaling, target_
228229
start = time.perf_counter()
229230
# CV2 is only 2D but we're resizing from the 1D image anyway at the moment.
230231
new_volume = cv2.resize(
231-
np_convert(dtype, vol.reshape(shape.Y, shape.X), normalize=False, safe_bool=True),
232+
np_convert(dtype, vol.T.reshape(shape.Y, shape.X, shape.C), normalize=False, safe_bool=True),
232233
None, fx=scaling[1], fy=scaling[2], interpolation=interpolation)
233234
perf["Zoom"] = time.perf_counter() - start
234235
start = time.perf_counter()
@@ -237,7 +238,7 @@ def write_conv_vol(writer: callable, source_path, shape, dtype, scaling, target_
237238
perf["Write Merged"] = time.perf_counter() - start
238239
else:
239240
start = time.perf_counter()
240-
writer(target_folder.joinpath(f"{index}"),
241-
data=np_convert(dtype, vol.reshape(shape.Y, shape.X), normalize=False, safe_bool=True))
241+
writer(target_folder.joinpath(f"{index}.tif"),
242+
data=np_convert(dtype, vol.T.reshape(shape.Y, shape.X, shape.C), normalize=False, safe_bool=True))
242243
perf["Write Merged"] = time.perf_counter() - start
243244
return perf

python/ouroboros/helpers/shapes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ def of(cls, source: DataShape):
208208
@dataclass
209209
class ImgSlice(DataShape): Y: int; X: int # noqa: E701,E702
210210
@dataclass
211+
class ImgSliceC(DataShape): Y: int; X: int; C: int # noqa: E701,E702
212+
@dataclass
211213
class Proj(DataShape): Y: int; X: int # noqa: E701,E702
212214
@dataclass
213215
class YSlice(DataShape): Theta: int; X: int # noqa: E701,E702

python/ouroboros/helpers/slice.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def slice_volume_from_grids(
168168
def _apply_weights(values, weights, corner):
169169
# This looks stupid but is twice as fast as fancy indexing with prod
170170
c_weights = weights[corner[0], 0, :] * weights[corner[1], 1, :] * weights[corner[2], 2, :]
171-
w_values = values * c_weights
171+
w_values = values * c_weights[..., np.newaxis]
172172
return w_values, c_weights
173173

174174

@@ -188,21 +188,23 @@ def backproject_box(bounding_box: BoundingBox, slice_rects: np.ndarray, slices:
188188
# No slices, just return
189189
return np.empty((0), dtype=np.uint32), np.empty(0, dtype=np.float32), np.empty(0, dtype=np.float32)
190190

191+
channels = 1 if len(slices.shape) < 4 else slices.shape[-1]
192+
191193
# Plot the backprojected X/Y/Z coordinates for each of the slice rects in this chunk.
192-
grid_call = partial(coordinate_grid, shape=slices[0].shape, floor=bounding_box.get_min(), flip=True)
194+
grid_call = partial(coordinate_grid, shape=slices[0].shape[:2], floor=bounding_box.get_min(), flip=True)
193195
precise_points = np.concatenate(list(map(grid_call, slice_rects)))
194196

195197
# Flatten values to 1-Dimension Array for Efficient Allocation.
196198
# Use ZYX domain rather than XYZ as the former is what is written to disk.
197-
values = slices.flatten()
199+
values = slices.flatten().reshape(-1, channels)
198200
zyx_shape = np.flip(bounding_box.get_shape())
199201
flat_shape = np.prod(zyx_shape)
200202

201203
# Determine minimum dtype for index of flattened shape.
202204
squish_type = np.min_scalar_type(flat_shape)
203205

204206
# Allocate the flattened data volume.
205-
volume = np.zeros((2, flat_shape), dtype=np.float32)
207+
volume = np.zeros((1 + channels, flat_shape), dtype=np.float32)
206208

207209
# Get the top corner (integer) points and full weight matrix.
208210
points, weights = _points_and_weights(precise_points.reshape(-1, 3).T, zyx_shape, squish_type)
@@ -212,14 +214,15 @@ def backproject_box(bounding_box: BoundingBox, slice_rects: np.ndarray, slices:
212214
w_values, c_weights = _apply_weights(values, weights, corner)
213215
point_inc = np.ravel_multi_index(corner, zyx_shape).astype(squish_type)
214216

215-
np.add.at(volume[0], points + point_inc, w_values)
216-
np.add.at(volume[1], points + point_inc, c_weights)
217+
for i in range(0, channels):
218+
np.add.at(volume[i], points + point_inc, w_values[..., i])
219+
np.add.at(volume[-1], points + point_inc, c_weights)
217220

218221
# Get indicies of the flattened Z-Y-X backprojected domain that have values.
219-
nz_vol = np.flatnonzero(volume[0])
222+
nz_vol = np.flatnonzero(volume[-1])
220223

221224
# Return indicies and only the volume region with values.
222-
return nz_vol, volume[0, nz_vol].squeeze(), volume[1, nz_vol].squeeze()
225+
return nz_vol, volume[:-1, nz_vol].squeeze(), volume[-1, nz_vol].squeeze()
223226

224227

225228
def make_volume_binary(volume: np.ndarray, dtype=np.uint8) -> np.ndarray:

python/ouroboros/pipeline/backproject_pipeline.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
write_conv_vol,
3535
write_small_intermediate
3636
)
37-
from ouroboros.helpers.shapes import DataRange, ImgSlice
37+
from ouroboros.helpers.shapes import DataRange, ImgSliceC
3838

3939

4040
DEFAULT_CHUNK_SIZE = 160
@@ -84,10 +84,12 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]:
8484
# tiff format check to add
8585
FPShape = FrontProjStack(D=len(list(Path(straightened_volume_path).iterdir())),
8686
V=tif.pages[0].shape[0], U=tif.pages[0].shape[1])
87+
channels = 1 if len(tif.pages[0].shape) < 3 else tif.pages[0].shape[-1]
8788
else:
8889
with tifffile.TiffFile(straightened_volume_path) as tif:
8990
is_compressed = bool(tif.pages[0].compression)
9091
FPShape = FrontProjStack(D=len(tif.pages), V=tif.pages[0].shape[0], U=tif.pages[0].shape[1])
92+
channels = 1 if len(tif.pages[0].shape) < 3 else tif.pages[0].shape[-1]
9193

9294
cannot_memmap = False
9395
try:
@@ -154,7 +156,8 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]:
154156
tif_write = partial(generate_tiff_write,
155157
compression=config.backprojection_compression,
156158
micron_resolution=volume_cache.get_resolution_um(),
157-
backprojection_offset=bp_offset)
159+
backprojection_offset=bp_offset,
160+
photometric="rgb" if channels > 1 else "minisblack")
158161

159162
if pipeline_input.slice_options.output_mip_level != config.output_mip_level:
160163
scaling_factors, _ = calculate_scaling_factors(
@@ -165,7 +168,6 @@ def _process(self, input_data: any) -> tuple[any, None] | tuple[None, any]:
165168
)
166169
else:
167170
scaling_factors = None
168-
print(f"SF: {scaling_factors}")
169171

170172
# Allocate procs equally between BP math and writing if we're rescaling, otherwise 3-1 favoring
171173
# the BP calculation.
@@ -233,7 +235,7 @@ def note_written(write_future):
233235
write_conv_vol,
234236
tif_write(tifffile.imwrite),
235237
i_path.joinpath(f"i_{index:05}"),
236-
ImgSlice(*write_shape[1:]),
238+
ImgSliceC(*write_shape[1:], channels),
237239
bool if config.make_backprojection_binary else np.uint16,
238240
scaling_factors,
239241
folder_path,
@@ -316,7 +318,7 @@ def process_chunk(
316318
durations["back_project"] = [time.perf_counter() - start]
317319
durations["total_bytes"] = [int(lookup.nbytes + values.nbytes + weights.nbytes)]
318320

319-
if len(values) == 0:
321+
if values.nbytes == 0:
320322
# No data to write from this chunk, so return as such.
321323
durations["total_process"] = [time.perf_counter() - start_total]
322324
return durations, index, []
@@ -354,7 +356,7 @@ def write_z(i, z_slice):
354356
file_path.joinpath(f"i_{offset_z:05}").mkdir(exist_ok=True, parents=True)
355357
write_small_intermediate(file_path.joinpath(f"i_{offset_z:05}", f"{index}.tif"),
356358
np.fromiter(offset_dict.values(), dtype=np.uint32, count=4),
357-
yx_vals[z_slice], values[z_slice], weights[z_slice])
359+
yx_vals[z_slice], np.atleast_2d(values)[:, z_slice], weights[z_slice])
358360

359361
with ThreadPool(12) as pool:
360362
pool.starmap(write_z, enumerate(z_slices))

0 commit comments

Comments
 (0)