Skip to content

Commit aace92c

Browse files
committed
Merge branch 'omezarr' into develop
2 parents 3d5ad21 + 750e5b3 commit aace92c

File tree

4 files changed

+570
-75
lines changed

4 files changed

+570
-75
lines changed

acpreprocessing/stitching_modules/convert_to_n5/acquisition_dir_to_n5_dir.py acpreprocessing/stitching_modules/convert_to_n5/acquisition_dir_to_ngff.py

+71-38
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
import json
77
import pathlib
88
import shutil
9+
from packaging import version
910

1011
import argschema
11-
12-
from acpreprocessing.stitching_modules.convert_to_n5.tiff_to_n5 import (
13-
tiffdir_to_n5_group,
14-
N5GenerationParameters
15-
)
12+
from acpreprocessing.stitching_modules.convert_to_n5.tiff_to_ngff import tiffdir_to_ngff_group, NGFFGenerationParameters
1613

1714

1815
def yield_position_paths_from_rootdir(
@@ -27,17 +24,40 @@ def yield_position_paths_from_rootdir(
2724
yield root_path / stripdir_bn
2825

2926

27+
def get_position_names_from_rootdir(
28+
root_dir, stripjson_bn="hh.log",
29+
stripjson_key="stripdirs"):
30+
root_path = pathlib.Path(root_dir)
31+
stripjson_path = root_path / stripjson_bn
32+
with stripjson_path.open() as f:
33+
stripjson_md = json.load(f)
34+
return stripjson_md[stripjson_key]
35+
36+
3037
def get_pixel_resolution_from_rootdir(
3138
root_dir, md_bn="acqinfo_metadata.json"):
3239
root_path = pathlib.Path(root_dir)
3340
md_path = root_path / md_bn
3441
with md_path.open() as f:
3542
md = json.load(f)
3643
xy = md["settings"]["pixel_spacing_um"]
37-
z = md["positions"][1]["x_step_um"]
44+
z = md["positions"][0]["x_step_um"]
3845
return [xy, xy, z]
3946

4047

48+
def get_strip_positions_from_rootdir(
49+
root_dir, md_bn="acqinfo_metadata.json"):
50+
root_path = pathlib.Path(root_dir)
51+
md_path = root_path / md_bn
52+
with md_path.open() as f:
53+
md = json.load(f)
54+
55+
if version.parse(md["version"]) >= version.parse("0.0.3"):
56+
return [(p["z_start_um"], p["y_start_um"], p["x_start_um"]) for p in md["positions"]]
57+
else:
58+
return [(0, p["y_start_um"], p["x_start_um"]) for p in md["positions"]]
59+
60+
4161
def get_number_interleaved_channels_from_rootdir(
4262
root_dir, md_bn="acqinfo_metadata.json"):
4363
root_path = pathlib.Path(root_dir)
@@ -49,29 +69,34 @@ def get_number_interleaved_channels_from_rootdir(
4969
return interleaved_channels
5070

5171

52-
def acquisition_to_n5(acquisition_dir, out_dir, concurrency=5,
53-
n5_generation_kwargs=None, copy_top_level_files=True):
72+
def acquisition_to_ngff(acquisition_dir, output, out_dir, concurrency=5,
73+
ngff_generation_kwargs=None, copy_top_level_files=True):
5474
"""
5575
"""
56-
n5_generation_kwargs = (
57-
{} if n5_generation_kwargs is None
58-
else n5_generation_kwargs)
76+
ngff_generation_kwargs = (
77+
{} if ngff_generation_kwargs is None
78+
else ngff_generation_kwargs)
5979

6080
acquisition_path = pathlib.Path(acquisition_dir)
6181
out_path = pathlib.Path(out_dir)
62-
out_n5_dir = str(out_path / f"{out_path.name}.n5")
82+
if output == 'zarr':
83+
output_dir = str(out_path / f"{out_path.name}.zarr")
84+
else:
85+
output_dir = str(out_path / f"{out_path.name}.n5")
6386

6487
interleaved_channels = get_number_interleaved_channels_from_rootdir(
6588
acquisition_path)
89+
positionList = get_strip_positions_from_rootdir(acquisition_path)
6690

6791
try:
68-
setup_group_attributes = {
92+
setup_group_attributes = [{
6993
"pixelResolution": {
7094
"dimensions": get_pixel_resolution_from_rootdir(
7195
acquisition_path),
7296
"unit": "um"
73-
}
74-
}
97+
},
98+
"position": p
99+
} for p in positionList]
75100
except (KeyError, FileNotFoundError):
76101
setup_group_attributes = {}
77102

@@ -85,16 +110,22 @@ def acquisition_to_n5(acquisition_dir, out_dir, concurrency=5,
85110
# pos_group = pospath.name
86111
# below is more like legacy structure
87112
# out_n5_dir = str(out_path / f"{pos_group}.n5")
113+
if output == 'zarr':
114+
group_names = [pospath.name]
115+
group_attributes = [setup_group_attributes[i]]
116+
else:
117+
group_names = [
118+
f"channel{channel_idx}", f"setup{i}", "timepoint0"]
119+
group_attributes = [channel_group_attributes,
120+
setup_group_attributes[i]]
121+
88122
futs.append(e.submit(
89-
tiffdir_to_n5_group,
90-
str(pospath), out_n5_dir, [
91-
f"channel{channel_idx}", f"setup{i}", "timepoint0"],
92-
group_attributes=[
93-
channel_group_attributes,
94-
setup_group_attributes],
123+
tiffdir_to_ngff_group,
124+
str(pospath), output, output_dir, group_names,
125+
group_attributes=group_attributes,
95126
interleaved_channels=interleaved_channels,
96127
channel=channel_idx,
97-
**n5_generation_kwargs
128+
**ngff_generation_kwargs
98129
))
99130

100131
for fut in concurrent.futures.as_completed(futs):
@@ -109,33 +140,35 @@ def acquisition_to_n5(acquisition_dir, out_dir, concurrency=5,
109140
shutil.copy(str(tlf_path), str(out_tlf_path))
110141

111142

112-
class AcquisitionDirToN5DirParameters(
113-
argschema.ArgSchema, N5GenerationParameters):
143+
class AcquisitionDirToNGFFParameters(
144+
argschema.ArgSchema, NGFFGenerationParameters):
114145
input_dir = argschema.fields.Str(required=True)
115-
output_dir = argschema.fields.Str(required=True)
146+
output_format = argschema.fields.Str(required=True)
147+
# output_dir = argschema.fields.Str(required=True)
116148
copy_top_level_files = argschema.fields.Bool(required=False, default=True)
117149
position_concurrency = argschema.fields.Int(required=False, default=5)
118150

119151

120-
class AcquisitionDirToN5Dir(argschema.ArgSchemaParser):
121-
default_schema = AcquisitionDirToN5DirParameters
152+
class AcquisitionDirToNGFF(argschema.ArgSchemaParser):
153+
default_schema = AcquisitionDirToNGFFParameters
122154

123-
def _get_n5_kwargs(self):
124-
n5_keys = {
155+
def _get_ngff_kwargs(self):
156+
ngff_keys = {
125157
"max_mip", "concurrency", "compression",
126-
"lvl_to_mip_kwargs", "chunk_size", "mip_dsfactor"}
127-
return {k: self.args[k] for k in (n5_keys & self.args.keys())}
158+
"lvl_to_mip_kwargs", "chunk_size", "mip_dsfactor",
159+
"deskew_options"}
160+
return {k: self.args[k] for k in (ngff_keys & self.args.keys())}
128161

129162
def run(self):
130-
n5_kwargs = self._get_n5_kwargs()
131-
acquisition_to_n5(
132-
self.args["input_dir"], self.args["output_dir"],
163+
ngff_kwargs = self._get_ngff_kwargs()
164+
acquisition_to_ngff(
165+
self.args["input_dir"], self.args["output_format"], self.args["output_file"],
133166
concurrency=self.args["position_concurrency"],
134-
n5_generation_kwargs=n5_kwargs,
167+
ngff_generation_kwargs=ngff_kwargs,
135168
copy_top_level_files=self.args["copy_top_level_files"]
136-
)
169+
)
137170

138171

139172
if __name__ == "__main__":
140-
mod = AcquisitionDirToN5Dir()
141-
mod.run()
173+
mod = AcquisitionDirToNGFF()
174+
mod.run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"""Pixel shift deskew
2+
implements (chunked) pixel shifting deskew
3+
skew_dims_zyx = dimensions of skewed (input) tiff data (xy are camera coordinates, z is tiff chunk #size, xz define skewed plane and y is non-skewed axis)
4+
stride = number of camera (x) pixels to shift onto a sample (z') plane (sample z dim = camera x #dim/stride)
5+
deskewFlip = flip volume (reflection, parity inversion)
6+
dtype = datatype of input data
7+
8+
NOTE: must be run sequentially as each tiff chunk contains data for the next deskewed block #retained in self.slice1d except for the final chunk which should form the rhomboid edge
9+
"""
10+
11+
import numpy as np
12+
13+
14+
def psdeskew_kwargs(skew_dims_zyx, deskew_stride=1, deskew_flip=False, deskew_crop=1, dtype='uint16', **kwargs):
15+
"""get keyword arguments for deskew_block
16+
17+
Parameters
18+
----------
19+
skew_dims_zyx : tuple of int
20+
dimensions of raw data array block to be deskewed
21+
stride : int
22+
number of camera pixels per deskewed sampling plane (divides z resolution)
23+
deskewFlip : bool
24+
flip data blocks before deskewing
25+
dtype : str
26+
datatype for deskew output
27+
crop_factor : float
28+
reduce y dimension according to ydim*crop_factor < ydim
29+
30+
Returns
31+
----------
32+
kwargs : dict
33+
parameters representing pixel deskew operation for deskew_block
34+
"""
35+
sdims = skew_dims_zyx
36+
crop_factor = deskew_crop
37+
stride = deskew_stride
38+
ydim = int(sdims[1]*crop_factor)
39+
blockdims = (int(sdims[2]/stride), ydim, stride*sdims[0])
40+
subblocks = int(np.ceil((sdims[2]+stride*sdims[0])/(stride*sdims[0])))
41+
# print(subblocks)
42+
blockx = sdims[0]
43+
dsi = []
44+
si = []
45+
for i_block in range(subblocks):
46+
sxv = []
47+
szv = []
48+
for sz in range(blockx):
49+
sxstart = i_block*stride*blockx-stride*sz
50+
sxend = (i_block+1)*stride*blockx-stride*sz
51+
if sxstart < 0:
52+
sxstart = 0
53+
if sxend > sdims[2]:
54+
sxend = sdims[2]
55+
sx = np.arange(sxstart, sxend)
56+
sxv.append(sx)
57+
szv.append(sz*np.ones(sx.shape, dtype=sx.dtype))
58+
sxv = np.concatenate(sxv)
59+
szv = np.concatenate(szv)
60+
dsx = sxv + stride*szv - i_block*stride*blockx
61+
dsz = np.floor(sxv/stride).astype(int)
62+
dsi.append(np.ravel_multi_index(
63+
(dsz, dsx), (blockdims[0], blockdims[2])))
64+
si.append(np.ravel_multi_index((szv, sxv), (sdims[0], sdims[2])))
65+
kwargs = {'dsi': dsi,
66+
'si': si,
67+
'slice1d': np.zeros((subblocks, blockdims[1], blockdims[2]*blockdims[0]), dtype=dtype),
68+
'blockdims': blockdims,
69+
'subblocks': subblocks,
70+
'flip': deskew_flip,
71+
'dtype': dtype,
72+
'chunklength': blockx
73+
}
74+
return kwargs
75+
76+
77+
def deskew_block(blockData, n, dsi, si, slice1d, blockdims, subblocks, flip, dtype, chunklength, *args, **kwargs):
78+
"""deskew a data chunk in sequence with prior chunks
79+
80+
Parameters
81+
----------
82+
blockData : numpy.ndarray
83+
block of raw (nondeskewed) data to be deskewed
84+
n : int
85+
current iteration in block sequence (must be run sequentially)
86+
dsi : numpy.ndarray
87+
deskewed indices for reslicing flattened data
88+
si : numpy.ndarray
89+
skewed indices for sampling flattened raw data
90+
slice1d : numpy.ndarray
91+
flattened data from previous iteration containing data for next deskewed block
92+
blockdims : tuple of int
93+
dimensions of output block
94+
subblocks : int
95+
number of partitions of input block for processing - likely not necessary
96+
flip : bool
97+
deskew flip
98+
dtype : str
99+
datatype
100+
chunklength : int
101+
number of slices expected for raw data block (for zero filling)
102+
103+
Returns
104+
----------
105+
block3d : numpy.ndarray
106+
pixel shifted deskewed data ordered (z,y,x) by sample axes
107+
"""
108+
subb = subblocks
109+
block3d = np.zeros(blockdims, dtype=dtype)
110+
zdim = block3d.shape[0]
111+
ydim = block3d.shape[1]
112+
xdim = block3d.shape[2]
113+
# crop blockData if needed
114+
if blockData.shape[1] > ydim:
115+
y0 = int(np.floor((blockData.shape[1]-ydim)/2))
116+
y1 = int(np.floor((blockData.shape[1]+ydim)/2))
117+
blockData = blockData[:, y0:y1, :]
118+
#print('deskewing block ' + str(n) + ' with shape ' + str(blockData.shape))
119+
if blockData.shape[0] < chunklength:
120+
#print('block is short, filling with zeros')
121+
blockData = np.concatenate((blockData, np.zeros(
122+
(int(chunklength-blockData.shape[0]), blockData.shape[1], blockData.shape[2]))))
123+
order = (np.arange(subb)+n) % subb
124+
for y in range(ydim):
125+
for i, o in enumerate(order):
126+
# flip stack axis 2 for ispim2
127+
s = -1 if flip else 1
128+
slice1d[o, y, :][dsi[i]] = blockData[:, y, ::s].ravel()[si[i]]
129+
block3d[:, y, :] = slice1d[n % subb, y, :].reshape((zdim, xdim))
130+
slice1d[n % subb, y, :] = 0
131+
return block3d
132+
133+
134+
def reshape_joined_shapes(joined_shapes, stride, blockdims, *args, **kwargs):
135+
"""get dimensions of deskewed joined shapes from skewed joined shapes
136+
137+
Parameters
138+
----------
139+
joined_shapes : tuple of int
140+
shape of 3D array represented by concatenating mimg_fns
141+
stride : int
142+
number of camera pixels per deskewed sampling plane (divides z resolution)
143+
blockdims : tuple of int
144+
dimensions of output block
145+
146+
Returns
147+
----------
148+
deskewed_shape : tuple of int
149+
shape of deskewed 3D array represented by joined_shapes
150+
"""
151+
deskewed_shape = (int(np.ceil(joined_shapes[0]/(blockdims[2]/stride))*blockdims[2]),
152+
blockdims[1],
153+
blockdims[0])
154+
return deskewed_shape

0 commit comments

Comments
 (0)