Skip to content

Commit 54a33e3

Browse files
authoredFeb 15, 2023
Merge pull request #43 from AllenInstitute/develop
Develop
2 parents ea42de2 + 43b9d1e commit 54a33e3

23 files changed

+1262
-429
lines changed
 

‎acpreprocessing/stitching_modules/convert_to_n5/acquisition_dir_to_n5_dir.py

+42-20
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ def get_pixel_resolution_from_rootdir(
3838
return [xy, xy, z]
3939

4040

41+
def get_number_interleaved_channels_from_rootdir(
42+
root_dir, md_bn="acqinfo_metadata.json"):
43+
root_path = pathlib.Path(root_dir)
44+
md_path = root_path / md_bn
45+
with md_path.open() as f:
46+
md = json.load(f)
47+
48+
interleaved_channels = md.get("channels", 1)
49+
return interleaved_channels
50+
51+
4152
def acquisition_to_n5(acquisition_dir, out_dir, concurrency=5,
4253
n5_generation_kwargs=None, copy_top_level_files=True):
4354
"""
@@ -50,33 +61,44 @@ def acquisition_to_n5(acquisition_dir, out_dir, concurrency=5,
5061
out_path = pathlib.Path(out_dir)
5162
out_n5_dir = str(out_path / f"{out_path.name}.n5")
5263

64+
interleaved_channels = get_number_interleaved_channels_from_rootdir(
65+
acquisition_path)
66+
5367
try:
54-
group_attributes = {
68+
setup_group_attributes = {
5569
"pixelResolution": {
5670
"dimensions": get_pixel_resolution_from_rootdir(
5771
acquisition_path),
5872
"unit": "um"
5973
}
6074
}
6175
except (KeyError, FileNotFoundError):
62-
group_attributes = {}
63-
64-
with concurrent.futures.ProcessPoolExecutor(max_workers=concurrency) as e:
65-
futs = []
66-
for i, pospath in enumerate(
67-
yield_position_paths_from_rootdir(
68-
acquisition_path)):
69-
# pos_group = pospath.name
70-
# below is more like legacy structure
71-
# out_n5_dir = str(out_path / f"{pos_group}.n5")
72-
futs.append(e.submit(
73-
tiffdir_to_n5_group,
74-
str(pospath), out_n5_dir, [f"setup{i}", "timepoint0"],
75-
group_attributes=[group_attributes], **n5_generation_kwargs
76-
))
77-
78-
for fut in concurrent.futures.as_completed(futs):
79-
_ = fut.result()
76+
setup_group_attributes = {}
77+
78+
for channel_idx in range(interleaved_channels):
79+
channel_group_attributes = {}
80+
with concurrent.futures.ProcessPoolExecutor(max_workers=concurrency) as e:
81+
futs = []
82+
for i, pospath in enumerate(
83+
yield_position_paths_from_rootdir(
84+
acquisition_path)):
85+
# pos_group = pospath.name
86+
# below is more like legacy structure
87+
# out_n5_dir = str(out_path / f"{pos_group}.n5")
88+
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],
95+
interleaved_channels=interleaved_channels,
96+
channel=channel_idx,
97+
**n5_generation_kwargs
98+
))
99+
100+
for fut in concurrent.futures.as_completed(futs):
101+
_ = fut.result()
80102

81103
if copy_top_level_files:
82104
top_level_files_paths = [
@@ -116,4 +138,4 @@ def run(self):
116138

117139
if __name__ == "__main__":
118140
mod = AcquisitionDirToN5Dir()
119-
mod.run()
141+
mod.run()

‎acpreprocessing/stitching_modules/convert_to_n5/tiff_to_n5.py

+296-19
Large diffs are not rendered by default.

‎acpreprocessing/stitching_modules/multiscale_viewing/multiscale.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import argschema
33
from argschema.fields import NumpyArray, Int, Str
4-
from acpreprocessing.stitching_modules.metadata import parse_metadata
4+
from acpreprocessing.utils.metadata import parse_metadata
55
from acpreprocessing.utils import io
66

77
example_input = {
@@ -23,14 +23,14 @@ class MultiscaleSchema(argschema.ArgSchema):
2323
description='name of metadata json file')
2424

2525

26-
# fix n5 version in attributes json (overwrites)
2726
def fix_version(outputRoot):
27+
"""fix n5 version in attributes json (overwrites)"""
2828
att = {"n5": "2.5.0"}
2929
io.save_metadata(outputRoot+f"/attributes.json", att)
3030

3131

32-
# add downsampling factor key/value to attributes json
3332
def add_downsampling_factors(outputRoot, pos, max_mip):
33+
"""add downsampling factor key/value to attributes json"""
3434
for mip_level in range(1, max_mip+1):
3535
factor = [2**mip_level, 2**mip_level, 2**mip_level]
3636
d = {"downsamplingFactors": factor}
@@ -42,8 +42,8 @@ def add_downsampling_factors(outputRoot, pos, max_mip):
4242
att)
4343

4444

45-
# add attribute file to mutirespos folders and create symlinks
4645
def add_multiscale_attributes(outputRoot, pixelResolution, position, max_mip):
46+
"""add attribute file to mutirespos folders and create symlinks"""
4747
multires_att = os.path.join(outputRoot +
4848
f"/multirespos{position}/attributes.json")
4949
if not os.path.isfile(multires_att):

‎acpreprocessing/stitching_modules/nglink/create_layer.py

-108
This file was deleted.

‎acpreprocessing/stitching_modules/nglink/update_state.py

-88
This file was deleted.

‎acpreprocessing/stitching_modules/stitch/create_json.py

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from acpreprocessing.stitching_modules.metadata import parse_metadata
1+
from argschema.fields import Int, Str, Boolean
22
from argschema.fields import Int, Str
3+
from acpreprocessing.utils.metadata import parse_metadata
4+
from argschema.fields import Int, Str, Boolean
35
import argschema
46
from acpreprocessing.utils import io
57
import os
@@ -8,7 +10,8 @@
810
example_input = {
911
"rootDir": "/ACdata/processed/demoModules/raw/",
1012
"outputDir": "/ACdata/processed/demoModules/output/",
11-
"mip_level": 2
13+
"mip_level": 2,
14+
"reverse": False
1215
}
1316

1417

@@ -17,19 +20,28 @@ class CreateJsonSchema(argschema.ArgSchema):
1720
outputDir = Str(required=True, description='output directory')
1821
mip_level = Int(required=False, default=2,
1922
description='downsample level to perform stitching with')
23+
reverse = Boolean(required=False, description='Whether position strips should be placed in reverse order')
24+
dirname = Str(required=True, description="name of dataset folder")
25+
stitch_channel = Int(required=False,default=0, description="Which channel to compute stitching with")
26+
stitch_json = Str(required=False,default="stitch.json", description="Name of stitching parameters json file")
2027

2128

22-
# Create specific position strip information needed for stitching
23-
# (including approximate coordinates using overlap)
24-
def get_pos_info(downdir, overlap, pr, ind, mip_level):
29+
def get_pos_info(downdir, overlap, pr, ind, mip_level, reverse):
30+
"""Create specific position strip information needed for stitching
31+
(including approximate tile coordinates using overlap)
32+
"""
2533
factor = 2**mip_level
2634
att = io.read_json(downdir+"attributes.json")
2735
sz = att["dimensions"]
2836
yshift = sz[0]-overlap/factor
2937
if att['dataType'] == 'uint16':
3038
dtype = 'GRAY16'
31-
pos_info = {"file": downdir, "index": ind, "pixelResolution": pr,
32-
"position": [0, ind*yshift, 0], "size": sz, "type": dtype}
39+
if reverse:
40+
pos_info = {"file": downdir, "index": ind, "pixelResolution": pr,
41+
"position": [0, -1*ind*yshift, 0], "size": sz, "type": dtype}
42+
else:
43+
pos_info = {"file": downdir, "index": ind, "pixelResolution": pr,
44+
"position": [0, ind*yshift, 0], "size": sz, "type": dtype}
3345
return pos_info
3446

3547

@@ -47,15 +59,15 @@ def run(self):
4759
for pos in range(0, n_pos):
4860
# downdir = self.args['outputDir'] + f"/Pos{pos}.n5/multirespos{pos}/s2/"
4961
downdir = posixpath.join(
50-
self.args["outputDir"],
51-
f"Pos{pos}.n5/multirespos{pos}/s{self.args['mip_level']}/")
62+
self.args["outputDir"]+self.args["dirname"]+".n5/",
63+
f"channel{self.args['stitch_channel']}/setup{pos}/timepoint0/s{self.args['mip_level']}/")
5264
# print(downdir)
5365
pos_info = get_pos_info(downdir, md.get_overlap(),
5466
md.get_pixel_resolution(), pos,
55-
self.args['mip_level'])
67+
self.args['mip_level'], self.args["reverse"])
5668
stitching_json.append(pos_info)
5769

58-
fout = os.path.join(self.args['outputDir'], 'stitch.json')
70+
fout = os.path.join(self.args['outputDir'], self.args['stitch_json'])
5971
io.save_metadata(fout, stitching_json)
6072

6173

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from cmath import phase
2+
from acpreprocessing.utils import io
3+
import argschema
4+
from argschema.fields import Str
5+
import os
6+
import matplotlib.pyplot as plt
7+
import numpy as np
8+
9+
example_input = {
10+
"stitchDir": "/ACdata/processed/iSPIM2/MN8_S10_220211_high_res/stitch-s3/iter0",
11+
"filename": "pairwise-stitched.json",
12+
"d_name": "MN8_S10_220211_high_res",
13+
"save_dir": "/ACdata/processed/iSPIM2/MN8_S10_220211_high_res/stitch-s3/stitching_eval/"
14+
}
15+
16+
def normalize(data):
17+
norm_data = []
18+
dmin, dmax = min(data), max(data)
19+
for i, val in enumerate(data):
20+
norm_data.append((val-dmin) / (dmax-dmin))
21+
return norm_data
22+
23+
class GraphStitchCorr(argschema.ArgSchema):
24+
stitchDir = Str(required=True, description='stitch iter directory')
25+
filename = Str(required=False, default="pairwise-stitched.json", description='which json file to grab stitch data from')
26+
d_name = Str(required=True, description='dataset name')
27+
28+
class GraphStitchCorr(argschema.ArgSchemaParser):
29+
default_schema = GraphStitchCorr
30+
31+
def run(self):
32+
pairwise = io.read_json(os.path.join(self.args['stitchDir'], self.args["filename"]))
33+
n_pairs= len(pairwise)
34+
labels = []
35+
corrs = []
36+
variances = []
37+
displacement_z = []
38+
# displacement_x = []
39+
# displacement_y = []
40+
# phase_corrs = []
41+
for pair in pairwise:
42+
pair_label="{}&{}".format(pair[0]["tilePair"]["tilePair"][0]["index"],pair[0]["tilePair"]["tilePair"][1]["index"])
43+
labels.append(pair_label)
44+
variances.append(pair[0]["variance"])
45+
corrs.append(pair[0]["crossCorrelation"])
46+
# displacement_x.append(abs(pair[0]["displacement"][0]))
47+
# displacement_y.append(abs(pair[0]["displacement"][1]))
48+
displacement_z.append(abs(pair[0]["displacement"][2]))
49+
# phase_corrs.append(pair[0]["phaseCorrelation"])
50+
51+
52+
norm_var = normalize(variances)
53+
# norm_displ_x = normalize(displacement_x)
54+
# norm_displ_y = normalize(displacement_y)
55+
norm_displ_z = normalize(displacement_z)
56+
# norm_phase = normalize(phase_corrs)
57+
58+
plt.plot(labels, corrs, linestyle='--', marker='x', label="cross correlation")
59+
plt.legend(loc="upper left")
60+
plt.xticks(rotation = 90)
61+
plt.xlabel("Tile Pair")
62+
plt.ylabel("Cross Correlation")
63+
plt.title("Cross correlation for {}".format(self.args['d_name']))
64+
plt.savefig(self.args["save_dir"]+'crossCorr.png', bbox_inches = "tight")
65+
66+
plt.figure(2)
67+
plt.plot(labels, corrs, linestyle='--', marker='x', label="cross correlation")
68+
# plt.plot(labels, norm_displ_x, linestyle='--', marker='+', label="norm displacement x")
69+
# plt.plot(labels, norm_displ_y, linestyle='--', marker='+', label="norm displacement y")
70+
plt.plot(labels, norm_displ_z, linestyle='--', marker='+', label="norm displacement z")
71+
plt.legend(loc="upper left")
72+
plt.xticks(rotation = 90)
73+
plt.xlabel("Tile Pair")
74+
plt.ylabel("Values 0-1")
75+
plt.title("Cross correlation and Z Normalized Displacements for {}".format(self.args['d_name']))
76+
plt.savefig(self.args["save_dir"]+'with_z_displ.png', bbox_inches = "tight")
77+
78+
plt.figure(3)
79+
plt.plot(labels, corrs, linestyle='--', marker='x', label="cross correlation")
80+
plt.plot(labels, norm_var, linestyle='--', marker='.', label="norm variance")
81+
plt.legend(loc="lower left")
82+
plt.xticks(rotation = 90)
83+
plt.xlabel("Tile Pair")
84+
plt.ylabel("Values 0-1")
85+
plt.title("Cross correlation and Normalized Variance for {}".format(self.args['d_name']))
86+
plt.savefig(self.args["save_dir"]+'with_norm_vars.png', bbox_inches = "tight")
87+
88+
# plt.figure(4)
89+
# plt.plot(labels, corrs, linestyle='--', marker='x', label="cross correlation")
90+
# plt.plot(labels, phase_corrs, linestyle='--', marker='.', label="phase correlation")
91+
# plt.legend(loc="upper left")
92+
# plt.xticks(rotation = 90)
93+
# plt.xlabel("Tile Pair")
94+
# plt.ylabel("Values 0-1")
95+
# plt.title("Cross correlation and Phase Correlation for {}".format(self.args['d_name']))
96+
# plt.savefig(self.args["save_dir"]+'with_norm_phase.png', bbox_inches = "tight")
97+
98+
99+
if __name__ == '__main__':
100+
mod = GraphStitchCorr(example_input)
101+
mod.run()

‎acpreprocessing/stitching_modules/stitch/stitch.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
"stitchjson": "/ACdata/processed/demoModules/output/stitch.json"
77
}
88

9+
910
def stitch(stitchjson):
11+
"""Call to stitching-spark stitch script"""
1012
subprocess.call(["python",
1113
"/ACdata/stitching-spark/startup-scripts/spark-local/"
1214
"stitch.py", "-i", stitchjson])

‎acpreprocessing/utils/8bit.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
Basic script to convert existing n5 data to 8bit
3+
Used this for evaluation/experimentation of methods
4+
5+
@author: shbhas
6+
"""
7+
8+
import numpy as np
9+
import z5py
10+
11+
output_n5 = "/ACdata/processed/MN7_RH_3_9_S33_220405_high_res/MN7_RH_3_9_S33_220405_high_res.n5/"
12+
out_8bit = "/ACdata/processed/MN7_RH_3_9_S33_220405_high_res8bit/MN7_RH_3_9_S33_220405_high_res8bit.n5"
13+
fout = z5py.File(out_8bit)
14+
15+
for s in range(18,19):
16+
with z5py.File(output_n5, mode='r') as f:
17+
s0shape = f[f"setup{s}"]['timepoint0']["s5"].shape
18+
t = 4
19+
u = 9
20+
xyf = s0shape[1]/t
21+
zf = s0shape[0]/u
22+
print(s0shape)
23+
num_ints = np.iinfo(np.uint16).max + 1
24+
lut = np.uint8(np.sqrt(np.arange(num_ints)))
25+
26+
fout.create_group(f"setup{s}")
27+
fout[f"setup{s}"].create_group("timepoint0")
28+
ds = fout[f"setup{s}"]["timepoint0"].create_dataset("s5", shape=s0shape, chunks = (int(zf),int(xyf),int(xyf)), dtype='uint8')
29+
30+
for x in range(t):
31+
for y in range(t):
32+
for z in range(u):
33+
a = int(z*zf)
34+
bx = int(x*xyf)
35+
by = int(y*xyf)
36+
imvol = f[f"setup{s}"]['timepoint0']["s5"][a:int(a+zf), bx:int(bx+xyf), by:int(by+xyf)]
37+
fout[f"setup{s}"]["timepoint0"]["s5"][a:int(a+zf), bx:int(bx+xyf), by:int(by+xyf)] = lut[imvol]
38+
# print((a,int(a+zf),bx,int(bx+xyf),by,int(by+xyf)))

‎acpreprocessing/stitching_modules/metadata/parse_metadata.py ‎acpreprocessing/utils/metadata/parse_metadata.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -24,35 +24,50 @@ def __init__(self, input_data=example_parsemetadata_input):
2424
self.rootDir = mod.args["rootDir"]
2525
self.md = io.read_json(os.path.join(self.rootDir, mod.args["fname"]))
2626

27-
# Return metadata json
2827
def get_md(self):
28+
"""Return entire metadata json"""
2929
return self.md
3030

31-
# Return x,y,z pixel resolution in um
3231
def get_pixel_resolution(self):
32+
"""Return x,y,z pixel resolution in um"""
3333
xy = self.md['settings']['pixel_spacing_um']
3434
z = self.md['positions'][1]['x_step_um']
3535
return [xy, xy, z]
3636

37-
# Return strip overlap in pixels
3837
def get_overlap(self):
38+
"""Return strip overlap in pixels - needed for stitching input"""
3939
return self.md['settings']['strip_overlap_pixels']
4040

41-
# Return number of position strips
4241
def get_number_of_positions(self):
42+
"""Return number of position strips in section"""
4343
return len(self.md['positions'])
4444

45-
# Get tiff stack width, height, and number of frames
45+
def get_number_of_channels(self):
46+
"""Return number of channels of data for section"""
47+
return self.md['channels']
48+
49+
def get_direction(self):
50+
"""Get direction of y direction for position strips in section"""
51+
if (self.md["positions"][0]["y_start_um"]) > (self.md["positions"][1]["y_start_um"]):
52+
return True
53+
else:
54+
return False
55+
4656
def get_size(self):
57+
"""Get tiff width, height, and number of frames in stack"""
4758
sz = [self.md['settings']['image_size_xy'][0],
4859
self.md['settings']['image_size_xy'][1],
4960
self.md['settings']['frames_per_file']]
5061
return sz
5162

52-
# Get data type
5363
def get_dtype(self):
64+
"""Get data type (uint8, uint16, ...)"""
5465
return self.md['settings']['dtype']
5566

67+
def get_angle(self):
68+
"""Get imaging sheet angle - needed for deskewing"""
69+
return self.md['settings']['sheet_angle']
70+
5671

5772
if __name__ == '__main__':
5873
mod = ParseMetadata(input_data=example_parsemetadata_input)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
from acpreprocessing.utils.metadata import parse_metadata
2+
from argschema.fields import Str, Boolean, Int
3+
import argschema
4+
5+
example_input = {
6+
"outputDir": "/ACdata/processed/demoModules/output/",
7+
"position": 2,
8+
"rootDir": "/ACdata/processed/demoModules/raw/",
9+
"md_filename": "acqinfo_metadata.json",
10+
"resolution": "high_res"
11+
}
12+
13+
14+
# Creates an image layer in neuroglancer that points to the n5 data of a specific position
15+
def create_layer(outputDir, position, ypos, pixelResolution, deskew, channel):
16+
layer_info = {"type": "image", "name": "ch"+str(channel)}
17+
layer_info["shader"] = "#uicontrol invlerp normalized\n#uicontrol float power slider(default=0.5, min=0, max=2)\n\nfloat somepower(float v, float power){\n return pow(v, power);\n }\nvoid main() {\n emitGrayscale(somepower(normalized(), power));\n}\n"
18+
layer_info["shaderControls"] = {"normalized": {"range": [0, 2000]}}
19+
url = "n5://http://bigkahuna.corp.alleninstitute.org"
20+
# os.path.join not working as I thought here?
21+
url = url + outputDir + '/setup%d/timepoint0/' % (position)
22+
layer_info["source"] = [{"url": url}]
23+
layer_info["name"] = "channel%d pos%d" % (channel, position)
24+
layer_info["source"][0]["transform"] = {
25+
"matrix": [
26+
[1, 0, 0, 0],
27+
[0, 1, 0, ypos*position],
28+
[deskew, 0, 1, 0]
29+
],
30+
"outputDimensions": {"x": [pixelResolution[0], "um"],
31+
"y": [pixelResolution[1], "um"],
32+
"z": [pixelResolution[2], "um"]}
33+
}
34+
return layer_info
35+
36+
37+
def add_source(outputDir, position, ypos, pixelResolution, state, deskew, channel):
38+
url = "n5://http://bigkahuna.corp.alleninstitute.org"
39+
url = url + outputDir + '/setup%d/timepoint0/' % (position)
40+
state["layers"][channel]["source"].append({"url": url})
41+
state["layers"][channel]["source"][position]["transform"] = {
42+
"matrix": [
43+
[1, 0, 0, 0],
44+
[0, 1, 0, ypos*position],
45+
[deskew, 0, 1, 0]
46+
],
47+
"outputDimensions": {"x": [pixelResolution[0], "um"],
48+
"y": [pixelResolution[1], "um"],
49+
"z": [pixelResolution[2], "um"]}
50+
}
51+
52+
53+
# Add layer to state
54+
def add_layer(state, layer_info):
55+
state["layers"].append(layer_info)
56+
57+
58+
class CreateLayerSchema(argschema.ArgSchema):
59+
position = argschema.fields.Int(default=0,
60+
description='position strip number')
61+
outputDir = argschema.fields.String(default='',
62+
description='output directory')
63+
rootDir = Str(required=True, description='raw tiff root directory')
64+
md_filename = Str(required=False, default="acqinfo_metadata.json",
65+
description='metadata file name')
66+
reverse = Boolean(required=False,default=False, description="Whether to reverse direction of stitching or not")
67+
deskew = Int(required=False,default=0, description="deskew factor (0 if want to leave undeskewed)")
68+
channel = Int(required=True, default=1, description="channel number")
69+
resolution = Str(default="high_res", description="whether data is high_res or overview")
70+
71+
class NgLayer(argschema.ArgSchemaParser):
72+
default_schema = CreateLayerSchema
73+
74+
def run(self, state=None):
75+
if state is None:
76+
state = {"layers": []}
77+
md_input = {
78+
"rootDir": self.args['rootDir'],
79+
"fname": self.args['md_filename']
80+
}
81+
md = parse_metadata.ParseMetadata(input_data=md_input)
82+
pr = md.get_pixel_resolution()
83+
sz = md.get_size()
84+
if self.args["resolution"] == "high_res":
85+
ypos = sz[1]-md.get_overlap() # subtract height of image by pixel overlap to get yposition
86+
elif self.args["resolution"] == "overview":
87+
ypos = sz[1]
88+
if self.args["reverse"]:
89+
layer0 = create_layer(self.args['outputDir'], self.args['position'],
90+
-1*ypos, pr, self.args['deskew'], self.args['channel'])
91+
else:
92+
layer0 = create_layer(self.args['outputDir'], self.args['position'],
93+
ypos, pr, self.args['deskew'], self.args['channel'])
94+
add_layer(state, layer0)
95+
96+
def run_consolidate(self, state=None):
97+
if state is None:
98+
state = {"layers": []}
99+
md_input = {
100+
"rootDir": self.args['rootDir'],
101+
"fname": self.args['md_filename']
102+
}
103+
md = parse_metadata.ParseMetadata(input_data=md_input)
104+
pr = md.get_pixel_resolution()
105+
sz = md.get_size()
106+
n_pos = md.get_number_of_positions()
107+
if self.args["resolution"] == "high_res":
108+
ypos = sz[1]-md.get_overlap() # subtract height of image by pixel overlap to get yposition
109+
elif self.args["resolution"] == "overview":
110+
ypos = sz[1]
111+
# Create the layer
112+
if self.args["reverse"]:
113+
layer = create_layer(self.args['outputDir'], self.args['position'],
114+
-1*ypos, pr, self.args['deskew'], self.args['channel'])
115+
else:
116+
layer = create_layer(self.args['outputDir'], self.args['position'],
117+
ypos, pr, self.args['deskew'], self.args['channel'])
118+
add_layer(state, layer)
119+
120+
for pos in range(1, n_pos):
121+
if self.args["reverse"]:
122+
add_source(self.args['outputDir'], pos, -1*ypos, pr, state, self.args['deskew'], self.args['channel'])
123+
else:
124+
add_source(self.args['outputDir'], pos, ypos, pr, state, self.args['deskew'], self.args['channel'])
125+
126+
127+
128+
129+
if __name__ == '__main__':
130+
mod = NgLayer(input_data=example_input).run()

‎acpreprocessing/stitching_modules/nglink/create_nglink.py ‎acpreprocessing/utils/nglink/create_nglink.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from acpreprocessing.stitching_modules.nglink import write_nglink
1+
from acpreprocessing.utils.nglink import write_nglink
22
from argschema.fields import Str
33
import argschema
44
import os
@@ -13,16 +13,16 @@
1313
class CreateNglinkSchema(argschema.ArgSchema):
1414
outputDir = Str(required=True, description='output directory')
1515
fname = Str(default="nglink.txt", description='output filename for nglink')
16+
state_json = Str(required=False,default="state.json", description="Name of overview state json file")
1617

1718

1819
class Nglink(argschema.ArgSchemaParser):
1920
default_schema = CreateNglinkSchema
2021

2122
def run(self, state):
2223
encoded_url = write_nglink.make_neuroglancer_url_vneurodata(state)
23-
write_nglink.write_url(self.args['outputDir'], self.args['fname'],encoded_url)
24-
# save state (overrite)
25-
f_out = os.path.join(self.args['outputDir'], 'state.json')
24+
write_nglink.write_url(self.args['outputDir'], self.args['fname'], encoded_url)
25+
f_out = os.path.join(self.args['outputDir'], self.args['state_json'])
2626
io.save_metadata(f_out, state)
2727

2828

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from acpreprocessing.utils.nglink import create_layer, create_nglink
2+
from argschema.fields import Dict
3+
import argschema
4+
import os
5+
6+
example_input = {
7+
"run_input": {
8+
"outputDir": "/ACdata/processed/demoModules/output/",
9+
"rootDir": "/ACdata/processed/demoModules/raw/",
10+
"ds_name": "MN6_2_S83_220531_high_res",
11+
"mip_level": 3,
12+
"md_filename": "/ACdata/processed/demoModules/raw/acqinfo_metadata.json",
13+
"consolidate_pos": True,
14+
"reverse_stitch": False,
15+
"deskew": False
16+
}
17+
}
18+
19+
20+
class CreateOverviewSchema(argschema.ArgSchema):
21+
run_input = argschema.fields.Dict(required=True, description='Input json for processing')
22+
23+
class Overview(argschema.ArgSchemaParser):
24+
default_schema = CreateOverviewSchema
25+
26+
def run(self, n_channels, n_pos, dirname, deskew, state=None):
27+
if not state:
28+
state = {"showDefaultAnnotations": False, "layers": []}
29+
for channel in range(n_channels):
30+
for pos in range(n_pos):
31+
if self.args["run_input"]["consolidate_pos"]:
32+
layer_input = {
33+
"position": 0,
34+
"outputDir": self.args["run_input"]['outputDir']+dirname+".n5/channel"+str(channel),
35+
"rootDir": self.args["run_input"]['rootDir'],
36+
"reverse": self.args["run_input"]["reverse_stitch"],
37+
"deskew": deskew,
38+
"channel": channel,
39+
"resolution": dirname.split("_")[-1]
40+
}
41+
create_layer.NgLayer(input_data=layer_input).run_consolidate(state)
42+
break
43+
else:
44+
layer_input = {
45+
"position": pos,
46+
"outputDir": self.args["run_input"]['outputDir']+dirname+".n5/channel"+str(channel),
47+
"rootDir": self.args["run_input"]['rootDir'],
48+
"reverse": self.args["run_input"]["reverse_stitch"],
49+
"deskew": deskew,
50+
"channel": channel,
51+
"resolution": dirname.split("_")[-1]
52+
}
53+
create_layer.NgLayer(input_data=layer_input).run(state)
54+
55+
# Create nglink from created state and estimated positions (overview link)
56+
nglink_input = {
57+
"outputDir": self.args["run_input"]['outputDir'],
58+
"fname": self.args["run_input"]["nglink_name"],
59+
"state_json": self.args["run_input"]["state_json"]
60+
}
61+
if not os.path.exists(os.path.join(nglink_input['outputDir'], nglink_input['fname'])):
62+
create_nglink.Nglink(input_data=nglink_input).run(state)
63+
else:
64+
print("nglink txt file already exists!")
65+
66+
67+
if __name__ == '__main__':
68+
mod = Overview(input_data=example_input).run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import posix
2+
from acpreprocessing.utils.metadata import parse_metadata
3+
from acpreprocessing.utils.nglink import create_nglink
4+
from argschema.fields import Int, Str, Boolean
5+
import argschema
6+
from acpreprocessing.utils import io
7+
import os
8+
9+
example_input = {
10+
'rootDir': "/ACdata/processed/demoModules/raw/",
11+
"outputDir": "/ACdata/processed/demoModules/output/",
12+
'mip_level': 3,
13+
"fname": "stitched-nglink.txt",
14+
"consolidate_pos": True
15+
}
16+
17+
18+
class UpdateStateSchema(argschema.ArgSchema):
19+
rootDir = Str(required=True, description='raw tiff root directory')
20+
outputDir = Str(default='', description='output directory')
21+
mip_level = Int(required=False, default=2,
22+
description='downsample level to perform stitching with')
23+
fname = Str(default="nglink.txt", description='output filename for nglink')
24+
consolidate_pos = Boolean(required=False, default=True,
25+
description="Whether to consolidate all position"
26+
"strips in one neuroglancer layer or"
27+
"separate them into independent layers")
28+
n_channels = Int(required=True,description="total number of channels in acquisition")
29+
state_json = Str(required=False,default="state.json", description="Name of overview state json file")
30+
stitch_final = Str(required=False,default="stitch-final.json", description="Name of final stitching result json file")
31+
stitched_state = Str(required=False,default="stitched_state.json", description="Name of stitched state json file")
32+
stitched_nglink = Str(required=False,default="stitched-nglink.txt", description="Name of stitched nglink txt file")
33+
34+
35+
# update statejson with stitched coordinates
36+
def update_positions(statejson, stitchoutjson, n_pos, factor, n_channels):
37+
for channel in range(0, n_channels):
38+
for pos in range(0, n_pos):
39+
try:
40+
statejson['layers'][pos+channel*n_pos]['source'][0]['transform']['matrix'][0][3] = stitchoutjson[pos]['position'][0]*factor
41+
statejson['layers'][pos+channel*n_pos]['source'][0]['transform']['matrix'][1][3] = stitchoutjson[pos]['position'][1]*factor
42+
statejson['layers'][pos+channel*n_pos]['source'][0]['transform']['matrix'][2][3] = stitchoutjson[pos]['position'][2]*factor
43+
except IndexError:
44+
print("Something went wrong with the stitching output!")
45+
# print(pos)
46+
47+
48+
def update_positions_consolidated(statejson, stitchoutjson, n_pos, factor, n_channels):
49+
for channel in range(0, n_channels):
50+
for pos in range(0, n_pos):
51+
# print(pos)
52+
try:
53+
statejson['layers'][channel]['source'][pos]['transform']['matrix'][0][3] = stitchoutjson[pos]['position'][0]*factor
54+
statejson['layers'][channel]['source'][pos]['transform']['matrix'][1][3] = stitchoutjson[pos]['position'][1]*factor
55+
statejson['layers'][channel]['source'][pos]['transform']['matrix'][2][3] = stitchoutjson[pos]['position'][2]*factor
56+
except IndexError:
57+
print("Something went wrong with the stitching output!")
58+
print(pos)
59+
60+
61+
class UpdateState(argschema.ArgSchemaParser):
62+
default_schema = UpdateStateSchema
63+
64+
def run(self):
65+
md_input = {
66+
"rootDir": self.args['rootDir']
67+
}
68+
n_pos = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_positions()
69+
statejson = io.read_json(os.path.join(self.args['outputDir'],
70+
self.args["state_json"]))
71+
stitchoutjson = io.read_json(os.path.join(self.args['outputDir'],
72+
self.args["stitch_final"]))
73+
if self.args["consolidate_pos"]:
74+
update_positions_consolidated(statejson, stitchoutjson,
75+
n_pos, 2**self.args['mip_level'], self.args["n_channels"])
76+
else:
77+
update_positions(statejson, stitchoutjson,
78+
n_pos, 2**self.args['mip_level'], self.args["n_channels"])
79+
80+
# create stitched nglink
81+
nglink_input = {
82+
"outputDir": self.args['outputDir'],
83+
"fname": self.args["stitched_nglink"]
84+
}
85+
if not os.path.exists(os.path.join(nglink_input['outputDir'], nglink_input["fname"])):
86+
create_nglink.Nglink(input_data=nglink_input).run(statejson)
87+
else:
88+
print(f"{nglink_input['fname']} already exists")
89+
90+
io.save_metadata(os.path.join(self.args['outputDir'],
91+
self.args["stitched_state"]), statejson)
92+
93+
94+
if __name__ == '__main__':
95+
mod = UpdateState(example_input)
96+
mod.run()

‎acpreprocessing/stitching_modules/nglink/write_nglink.py ‎acpreprocessing/utils/nglink/write_nglink.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ def write_tinyurl(outputDir, state, fname):
4040
ff = os.path.join(outputDir, fname)
4141
io.save_file(ff, url)
4242
print("Done! Neuroglancer Link:")
43-
print(url)
43+
print(url)
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
Basic script to split channels of acquisition (on tiff level)
3+
Used this before channel splitting was incorporated into n5 conversion
4+
5+
@author: shbhas
6+
"""
7+
8+
import os
9+
from natsort import natsorted, ns
10+
from acpreprocessing.utils import io
11+
from acpreprocessing.utils.metadata import parse_metadata
12+
13+
def sort_files(filedir):
14+
filelist = os.listdir(filedir)
15+
return natsorted(filelist, alg=ns.IGNORECASE)
16+
17+
outputdir = "/ACdata/processed/iSPIM2/MN7_RH_3_9_S19_220606_high_res/split_channels/"
18+
inputdir = "/ispim2_data/workflow_data/iSPIM2/MN7_RH_3_9_S19_220606_high_res/"
19+
md_input = {
20+
"rootDir": "/ispim2_data/workflow_data/iSPIM2/MN7_RH_3_9_S19_220606_high_res/",
21+
"fname": "acqinfo_metadata.json"
22+
}
23+
24+
n_pos = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_positions()
25+
print(n_pos)
26+
if not os.path.isdir(outputdir):
27+
os.makedirs(outputdir)
28+
29+
for s in range(n_pos):
30+
index = 0
31+
posdir = inputdir+f"high_res_Pos{s}/"
32+
for inputfile in sort_files(posdir):
33+
print(inputfile)
34+
if inputfile[0]=='.':
35+
continue
36+
I = io.get_tiff_image(posdir+inputfile)
37+
if index%2==0:
38+
chan0 = I[0::2, : , :]
39+
chan1 = I[1::2, : , :]
40+
else:
41+
chan1 = I[0::2, : , :]
42+
chan0 = I[1::2, : , :]
43+
fname0 = outputdir + f"ch0/high_res_Pos{s}/"+"{0:05d}.tif".format(index)
44+
fname1 = outputdir + f"ch1/high_res_Pos{s}/"+"{0:05d}.tif".format(index)
45+
print(fname0)
46+
io.save_tiff_image(chan0, fname0)
47+
io.save_tiff_image(chan1, fname1)
48+
index += 1

‎examples/example_dag.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""
2+
Example dag for automation of stitching
3+
last updated: 2022/08/17
4+
@author: shbhas
5+
"""
6+
from datetime import datetime, timedelta
7+
from textwrap import dedent
8+
9+
from airflow import DAG
10+
from airflow.operators.bash import BashOperator
11+
from airflow.operators.python import PythonOperator
12+
from airflow.sensors.external_task import ExternalTaskSensor
13+
14+
process_data_args = {
15+
'owner': 'shubhab',
16+
'depends_on_past': False,
17+
'email': ['shubha.bhaskaran@alleninstitute.org'],
18+
'email_on_failure': False,
19+
'email_on_retry': False,
20+
'retries': 1,
21+
'retry_delay': timedelta(minutes=5)
22+
}
23+
24+
with DAG(
25+
'stitch_data',
26+
default_args=process_data_args,
27+
description='Create overview and stitched links',
28+
schedule_interval=timedelta(days=1),
29+
start_date=datetime(2020, 11, 1),
30+
catchup=False,
31+
tags=['image_processing'],
32+
) as tiff2n5_dag:
33+
def run_convert(run_input):
34+
import os
35+
from acpreprocessing.stitching_modules.convert_to_n5 import acquisition_dir_to_n5_dir
36+
37+
convert_input = {
38+
"input_dir": run_input["rootDir"],
39+
"output_dir": run_input["outputDir"],
40+
"max_mip": 5,
41+
"position_concurrency": 5
42+
}
43+
dirname = run_input["ds_name"]
44+
if not os.path.isdir(convert_input['output_dir']):
45+
acquisition_dir_to_n5_dir.AcquisitionDirToN5Dir(input_data=convert_input).run()
46+
else:
47+
print(f"Skipping conversion, {dirname} directory already exists")
48+
49+
def run_create_overview(run_input):
50+
from acpreprocessing.utils.nglink import create_overview
51+
from acpreprocessing.utils.metadata import parse_metadata
52+
53+
md_input = {
54+
"rootDir": run_input['rootDir'],
55+
"fname": run_input['md_filename']
56+
}
57+
deskew = 0
58+
n_channels = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_channels()
59+
n_pos = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_positions()
60+
dirname = run_input["ds_name"]
61+
62+
state = {"showDefaultAnnotations": False, "layers": []}
63+
overview_input = {
64+
"run_input": run_input
65+
}
66+
create_overview.Overview(input_data=overview_input).run(n_channels, n_pos, dirname, deskew, state=state)
67+
68+
def run_stitch(run_input):
69+
from acpreprocessing.stitching_modules.stitch import create_json, stitch
70+
import os
71+
72+
create_json_input = {
73+
'rootDir': run_input['rootDir']+"/",
74+
'outputDir': run_input['outputDir']+"/",
75+
"mip_level": run_input['mip_level'],
76+
"reverse": run_input['reverse_stitch'],
77+
"dirname": run_input["ds_name"],
78+
"stitch_channel": run_input['stitch_channel'],
79+
"stitch_json": run_input['stitch_json']
80+
}
81+
create_json.CreateJson(input_data=create_json_input).run()
82+
83+
# Run Stitching with stitch.json
84+
stitchjsonpath = os.path.join(create_json_input['outputDir'], run_input['stitch_json'])
85+
stitch_input = {
86+
"stitchjson": stitchjsonpath
87+
}
88+
# Perform stitching if not done yet
89+
if not os.path.exists(os.path.join(create_json_input['outputDir'], run_input["stitch_final"])):
90+
stitch.Stitch(input_data=stitch_input).run()
91+
else:
92+
print("Skipped stitching - already computed")
93+
94+
def run_update_state(run_input):
95+
from acpreprocessing.utils.nglink import update_state
96+
from acpreprocessing.utils.metadata import parse_metadata
97+
md_input = {
98+
"rootDir": run_input['rootDir'],
99+
"fname": run_input['md_filename']
100+
}
101+
n_channels = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_channels()
102+
103+
update_state_input = {
104+
'rootDir': run_input['rootDir'],
105+
"outputDir": run_input['outputDir'],
106+
'mip_level': run_input['mip_level'],
107+
"fname": "stitched-nglink.txt",
108+
"consolidate_pos": run_input['consolidate_pos'],
109+
"n_channels": n_channels,
110+
"state_json": run_input["state_json"],
111+
"stitch_final": run_input["stitch_final"],
112+
"stitched_nglink": run_input["stitched_nglink"],
113+
"stitched_state": run_input["stitched_state"]
114+
}
115+
update_state.UpdateState(input_data=update_state_input).run()
116+
117+
task_convert = PythonOperator(
118+
task_id='convert',
119+
python_callable=run_convert,
120+
op_kwargs={"run_input": '{{ dag_run.conf }}'}
121+
)
122+
123+
task_create_overview = PythonOperator(
124+
task_id='create_overview',
125+
python_callable=run_create_overview,
126+
op_kwargs={"run_input": '{{ dag_run.conf }}'}
127+
)
128+
129+
task_stitch = PythonOperator(
130+
task_id='stitch',
131+
python_callable=run_stitch,
132+
op_kwargs={"run_input": '{{ dag_run.conf}}'}
133+
)
134+
135+
task_update_state = PythonOperator(
136+
task_id='update_state',
137+
python_callable=run_update_state,
138+
op_kwargs={"run_input": '{{ dag_run.conf}}'}
139+
)
140+
141+
task_convert >> task_create_overview
142+
task_create_overview >> task_stitch
143+
task_stitch >> task_update_state

‎examples/runmodules.py

-140
This file was deleted.

‎examples/runprocessing.py

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
"""
2+
Script to run stitching modules.
3+
Creates overview and stitched links for unprocessed acquisitions using the ddbclient
4+
5+
last updated: 2022/08/17
6+
@author: shbhas
7+
"""
8+
import time
9+
import os
10+
import numpy as np
11+
from acpreprocessing.utils.metadata import parse_metadata
12+
from acpreprocessing.stitching_modules.convert_to_n5 import acquisition_dir_to_n5_dir
13+
from acpreprocessing.utils.nglink import update_state, create_overview
14+
from acpreprocessing.stitching_modules.stitch import create_json, stitch
15+
import argschema
16+
from argschema.fields import Boolean, Int, Str
17+
from ddbclient import acquisition, client, utils
18+
import posixpath
19+
20+
21+
def find_entry(response_json):
22+
"""returns subset of responses that are high res (filters out overviews)"""
23+
for entry in response_json:
24+
uri = entry["data_location"]["ACTiff_dir_ispim"]["uri"]
25+
if uri.find("high_res") != -1:
26+
return [uri,entry["scope"]]
27+
return []
28+
29+
30+
start = time.time()
31+
32+
# ping db to find new samples to process
33+
acq_client = acquisition.AcquisitionClient("http://bigkahuna.corp.alleninstitute.org/transfer_temp/api/api", subpath="")
34+
35+
#for testing..
36+
# url_base = acq_client.base_url
37+
# # "http://api/acquisition/{acquisition_id}/update_fields"
38+
# stitching_status_dict = {"stitching_status": "STITCHING_COMPLETE"}
39+
# acq = response_json[0]
40+
# print(acq_client.base_url)
41+
# acq_id = acq["acquisition_id"]
42+
# url = posixpath.join(
43+
# url_base,
44+
# 'acquisition',
45+
# acq_id,
46+
# 'stitching_status'
47+
# utils.put_json(url, "STITCHING_COMPLETE")
48+
49+
# test_acq = {"section_num": "0", "session_id": "220722", "specimen_id": "test", "scope": "iSPIM2", "data_location": {"ACTiff_dir_m2": {"status": "DELETED", "uri": "", "metadata": {}},"ACTiff_dir_ispim": {"status": "COMPLETE", "uri": "qnap-ispim2:/workflow_data/iSPIM2/test_S0_220722_high_res", "metadata": {"contains": ["high_res_Pos0/high_res_0_0.tif", "high_res_Pos0/high_res_0_1.tif", "high_res_Pos0/high_res_0_2.tif"]}}}, "acquisition_metadata": {}, "acquisition_time_utc": "2022-07-22T19:50:03.717883+00:00", "acquisition_id": ""}
50+
# acq_client.post(test_acq)
51+
52+
response_json = acq_client.query(
53+
{
54+
"filter": {
55+
"stitching_status": {"status": {"$eq": "pending"}}
56+
# "specimen_id": "test"
57+
},
58+
"projection": {
59+
"specimen_id": True,
60+
"session_id": True,
61+
"section_num": True,
62+
"scope": True,
63+
"acquisition_id": True,
64+
"data_location": True,
65+
"stitching_status": True
66+
}
67+
})
68+
print(response_json)
69+
70+
if not response_json:
71+
print("Error: Could not find acquisition")
72+
else:
73+
info = find_entry(response_json)
74+
# print(info)
75+
if not info:
76+
"Error: Unable to find high_res data location"
77+
else:
78+
dir_ACdata = info[0]
79+
rootDir = "/ACdata"+dir_ACdata.split(':')[1]
80+
# print(rootDir)
81+
scope = info[1]
82+
dirname = rootDir.split("/")[-1]
83+
print(dirname)
84+
85+
run_input = {
86+
"outputDir": "/ACdata/processed/"+scope+"/"+dirname+"/",
87+
"rootDir": rootDir + "/",
88+
"ds_name": dirname,
89+
"mip_level": 3,
90+
"md_filename": rootDir+"/acqinfo_metadata.json",
91+
"consolidate_pos": True,
92+
"reverse_stitch": False,
93+
"deskew": False,
94+
"stitch_channel": 0,
95+
"stitch_json": "stitch-test.json",
96+
"stitch_final": "stitch-final.json",
97+
"state_json": "state-test.json",
98+
"nglink_name": "nglink-test.txt",
99+
"stitched_nglink": "stitched-nglink-test.txt",
100+
"stitched_state": "stitched-state.json"
101+
}
102+
print(run_input)
103+
104+
105+
class RunModulesSchema(argschema.ArgSchema):
106+
outputDir = Str(required=True, description='output directory')
107+
rootDir = Str(required=True, description='raw tiff root directory')
108+
ds_name = Str(required=True)
109+
mip_level = Int(required=False, default=3,
110+
description='downsample level to perform stitching with')
111+
md_filename = Str(required=False, default="acqinfo_metadata.json",
112+
description='metadata file name')
113+
consolidate_pos = Boolean(required=False, default=True,
114+
description="Whether to consolidate all position"
115+
"strips in one neuroglancer layer or"
116+
"separate them into independent layers")
117+
reverse_stitch = Boolean(required=False,default=False, description="Whether to reverse direction of stitching or not")
118+
deskew = Boolean(required=False,default=False, description="Whether to deskew or not")
119+
stitch_channel = Int(required=False,default=0, description="Which channel to compute stitching with")
120+
stitch_json = Str(required=False,default="stitch.json", description="Name of stitching parameters json file")
121+
stitch_final = Str(required=False,default="stitch-final.json", description="Name of final stitching result json file")
122+
state_json = Str(required=False,default="state.json", description="Name of overview state json file")
123+
nglink_name = Str(required=False,default="nglink.txt", description="Name of nglink txt file")
124+
stitched_nglink = Str(required=False,default="stitched-nglink.txt", description="Name of stitched nglink txt file")
125+
stitched_state = Str(required=False,default="stitched-state.json", description="Name of stitched state json file")
126+
127+
128+
class RunModules(argschema.ArgSchemaParser):
129+
default_schema = RunModulesSchema
130+
131+
def run(self):
132+
# set variables needed for processing
133+
md_input = {
134+
"rootDir": run_input['rootDir'],
135+
"fname": run_input['md_filename']
136+
}
137+
# md = parse_metadata.ParseMetadata(input_data=md_input).get_md()
138+
# dsname = md["stripdirs"][0].split("_Pos")[0]
139+
# run_input["ds_name"]=dsname
140+
run_input["reverse_stitch"]=parse_metadata.ParseMetadata(input_data=md_input).get_direction()
141+
deskew = 0
142+
if run_input['deskew']:
143+
deskew = np.cos(parse_metadata.ParseMetadata(input_data=md_input).get_angle())
144+
n_channels = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_channels()
145+
n_pos = parse_metadata.ParseMetadata(input_data=md_input).get_number_of_positions()
146+
if n_pos == 0:
147+
print("No positions to process")
148+
return -1
149+
150+
151+
# if not converted to n5, do so
152+
convert_input = {
153+
"input_dir": f"{run_input['rootDir']}",
154+
"output_dir": f"{run_input['outputDir']}",
155+
"max_mip": 5,
156+
"position_concurrency": 5
157+
}
158+
if not os.path.isdir(convert_input['output_dir']):
159+
acquisition_dir_to_n5_dir.AcquisitionDirToN5Dir(input_data=convert_input).run()
160+
else:
161+
print(f"Skipping conversion, {dirname} directory already exists")
162+
163+
164+
# Create overview nglink, initialize state for each channel
165+
state = {"showDefaultAnnotations": False, "layers": []}
166+
overview_input={
167+
"run_input": run_input
168+
}
169+
create_overview.Overview(input_data=overview_input).run(n_channels, n_pos, dirname, deskew, state=state)
170+
171+
172+
# Create Stitch.json which is input for stitching code (using channel 0)
173+
create_json_input = {
174+
'rootDir': run_input['rootDir']+"/",
175+
'outputDir': run_input['outputDir']+"/",
176+
"mip_level": run_input['mip_level'],
177+
"reverse": run_input['reverse_stitch'],
178+
"dirname": dirname,
179+
"stitch_channel": run_input['stitch_channel'],
180+
"stitch_json": run_input['stitch_json']
181+
}
182+
create_json.CreateJson(input_data=create_json_input).run()
183+
# Run Stitching with stitch.json
184+
stitchjsonpath = os.path.join(create_json_input['outputDir'], run_input['stitch_json'])
185+
stitch_input = {
186+
"stitchjson": stitchjsonpath
187+
}
188+
# Perform stitching if not done yet
189+
if not os.path.exists(os.path.join(run_input['outputDir'], run_input["stitch_final"])):
190+
stitch.Stitch(input_data=stitch_input).run()
191+
else:
192+
print("Skipped stitching - already computed")
193+
194+
195+
# update state json with stitched coord
196+
update_state_input = {
197+
'rootDir': run_input['rootDir'],
198+
"outputDir": run_input['outputDir'],
199+
'mip_level': run_input['mip_level'],
200+
"fname": "stitched-nglink.txt",
201+
"consolidate_pos": run_input['consolidate_pos'],
202+
"n_channels": n_channels,
203+
"state_json": run_input["state_json"],
204+
"stitch_final": run_input["stitch_final"],
205+
"stitched_nglink": run_input["stitched_nglink"],
206+
"stitched_state": run_input["stitched_state"]
207+
}
208+
update_state.UpdateState(input_data=update_state_input).run()
209+
210+
print('Done processing')
211+
print(str((time.time()-start)/60) + " minutes elapsed")
212+
213+
214+
if __name__ == '__main__':
215+
RunModules(run_input).run()

‎requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pillow
22
tifffile==2020.7.4
33
scikit-image
44
numpy
5-
imageio
5+
imageio<2.21
66
matplotlib
77
argschema
88
natsort

‎test/test_stitching_modules.py

+29-27
Original file line numberDiff line numberDiff line change
@@ -5,56 +5,58 @@
55
except ImportError:
66
# FIXME: z5py installs with conda, not currently configured on ci
77
pass
8-
from acpreprocessing.stitching_modules.metadata import parse_metadata
8+
from acpreprocessing.utils.metadata import parse_metadata
99
import acpreprocessing.stitching_modules.multiscale_viewing.multiscale
1010
import acpreprocessing.stitching_modules.stitch.create_json
1111
import acpreprocessing.stitching_modules.stitch.stitch
12-
from acpreprocessing.stitching_modules.nglink import create_layer
13-
from acpreprocessing.stitching_modules.nglink import update_state
14-
import acpreprocessing.stitching_modules.nglink.create_nglink
15-
import acpreprocessing.stitching_modules.nglink.write_nglink
12+
from acpreprocessing.utils.nglink import create_layer
13+
from acpreprocessing.utils.nglink import update_state
14+
import acpreprocessing.utils.nglink.create_nglink
15+
import acpreprocessing.utils.nglink.write_nglink
1616
from acpreprocessing.utils import io
1717
import os
1818

1919

2020
# skipped because we have changed the way layers are defined without updating this test
2121
@pytest.mark.skip
2222
# Test nglink.create_layer.create_layer
23-
@pytest.mark.parametrize("outputDir, position, overlap, pixelResolution",
24-
[("/testout", 2, 200, [0.1, 0.1, 0.1]),
25-
("/test2/testout/", 20, 500, [0.406, 0.406, 1.0])])
26-
def test_create_layer(outputDir, position, overlap, pixelResolution):
27-
layer = create_layer.create_layer(outputDir, position,
28-
overlap, pixelResolution)
23+
@pytest.mark.parametrize("outputDir, position, ypos, pixelResolution, deskew",
24+
[("/testout", 0, 200, [0.1, 0.1, 0.1], 0),
25+
("/test2/testout", 0, 500, [0.406, 0.406, 1.0], -2)])
26+
def test_create_layer(outputDir, position, ypos, pixelResolution, deskew):
27+
layer = create_layer.create_layer(outputDir, position, ypos, pixelResolution, deskew)
2928
url = "n5://http://bigkahuna.corp.alleninstitute.org"
30-
url = url + outputDir + '/Pos%d.n5/multirespos%d' % (position, position)
31-
assert layer["source"]["url"] == url
32-
assert layer["source"]["transform"]["matrix"][1][3] == overlap*position
33-
assert layer["source"]["transform"]["outputDimensions"]["x"][0] == pixelResolution[0]
34-
assert layer["source"]["transform"]["outputDimensions"]["y"][0] == pixelResolution[1]
35-
assert layer["source"]["transform"]["outputDimensions"]["z"][0] == pixelResolution[2]
29+
url = url + outputDir + '/setup%d/timepoint%d/' % (position, position)
30+
assert layer["source"][0]["url"] == url
31+
assert layer["source"][0]["transform"]["matrix"][1][3] == ypos*position
32+
assert layer["source"][0]["transform"]["matrix"][2][0] == deskew
33+
assert layer["source"][0]["transform"]["outputDimensions"]["x"][0] == pixelResolution[0]
34+
assert layer["source"][0]["transform"]["outputDimensions"]["y"][0] == pixelResolution[1]
35+
assert layer["source"][0]["transform"]["outputDimensions"]["z"][0] == pixelResolution[2]
36+
3637

3738

3839
# Test update state
3940
@pytest.mark.parametrize("x, y, z, overlap, factor", [(30, 500, 2, 1000, 2)])
4041
def test_update_state(x, y, z, overlap, factor):
41-
state = {'layers': []}
42-
state['layers'].append({'source': {'transform': {'matrix': [
42+
"""test for npos=1 and nchannel=1 only as of now"""
43+
state = {"showDefaultAnnotations": False, "layers": [{"source": [{"url": ""}]}]}
44+
state['layers'][0]["source"][0].update({'transform': {'matrix': [
4345
[1, 0, 0, 0],
4446
[0, 1, 0, 0],
4547
[0, 0, 1, 0]
46-
]}}})
47-
state['layers'][0]['source']['transform']['matrix'][0][3] = 0
48-
state['layers'][0]['source']['transform']['matrix'][1][3] = overlap
49-
state['layers'][0]['source']['transform']['matrix'][2][3] = 0
48+
]}})
49+
state['layers'][0]['source'][0]['transform']['matrix'][0][3] = 0
50+
state['layers'][0]['source'][0]['transform']['matrix'][1][3] = overlap
51+
state['layers'][0]['source'][0]['transform']['matrix'][2][3] = 0
5052
stitchoutjson = [{"type": "GRAY16", "index": 0, "file": "test",
5153
"position": [],
5254
"size": [288, 288, 1960],
5355
"pixelResolution":[0.406, 0.406, 2]}]
5456
stitchoutjson[0]['position'].append(x)
5557
stitchoutjson[0]['position'].append(y)
5658
stitchoutjson[0]['position'].append(z)
57-
update_state.update_positions(state, stitchoutjson, 1, factor)
58-
assert state['layers'][0]['source']['transform']['matrix'][0][3] == x*factor
59-
assert state['layers'][0]['source']['transform']['matrix'][1][3] == y*factor
60-
assert state['layers'][0]['source']['transform']['matrix'][2][3] == z*factor
59+
update_state.update_positions(state, stitchoutjson, 1, factor, 1)
60+
assert state['layers'][0]['source'][0]['transform']['matrix'][0][3] == x*factor
61+
assert state['layers'][0]['source'][0]['transform']['matrix'][1][3] == y*factor
62+
assert state['layers'][0]['source'][0]['transform']['matrix'][2][3] == z*factor

0 commit comments

Comments
 (0)
Please sign in to comment.