Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON output as string #28

Merged
merged 2 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions pytools/ng/build_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import SimpleITK as sitk
from pytools.utils import MutuallyExclusiveOption
from pytools import __version__
from math import floor, ceil


def weighted_quantile(values, quantiles, sample_weight=None, values_sorted=False, old_style=False):
Expand Down Expand Up @@ -66,7 +67,7 @@ def stream_build_histogram(filename: str, histogram_bin_edges, extract_axis=1, d
Read image slice by slice, and build a histogram. The image file must be readable by SimpleITK.
The SimpleITK is expected to support streaming the file format.

The np.histogram function is run on each image slice with the provided hitogram_bin_edges, and
The np.histogram function is run on each image slice with the provided histogram_bin_edges, and
accumulated for the results.

:param filename: The path to the image file to read. MRC file type is recommend.
Expand Down Expand Up @@ -166,6 +167,11 @@ def histogram_stats(hist, bin_edges):
mutually_exclusive=["sigma", "mad"],
help="Use INPUT_IMAGE's middle percentile (option's value) of data for minimum and maximum range.",
)
@click.option(
"--clamp/--no-clamp",
default=False,
help="Clamps minimum and maximum range to existing intensity values (floor and limit).",
)
@click.option(
"--output-json",
type=click.Path(exists=False, dir_okay=False, resolve_path=True),
Expand All @@ -174,12 +180,12 @@ def histogram_stats(hist, bin_edges):
"elements of a double numeric value.",
)
@click.version_option(__version__)
def main(input_image, mad, sigma, percentile, output_json):
def main(input_image, mad, sigma, percentile, clamp, output_json):
"""
Reads the INPUT_IMAGE to compute am estimated minimum and maximum range to be used for visualization of the
data set.
Reads the INPUT_IMAGE to compute an estimated minimum and maximum range to be used for visualization of the
data set. The image is required to have an integer pixel type.

The optional OUTPUT_JSON filename will be created with the following data elements with a double numeric value:
The optional OUTPUT_JSON filename will be created with the following data elements with integer values as strings:
"neuroglancerPrecomputedMin"
"neuroglancerPrecomputedMax"
"neuroglancerPrecomputedFloor"
Expand Down Expand Up @@ -246,12 +252,16 @@ def main(input_image, mad, sigma, percentile, output_json):

floor_limit = weighted_quantile(mids, quantiles=[0.0, 1.0], sample_weight=h, values_sorted=True)

if clamp:
min_max = (max(min_max[0], floor_limit[0]), min(min_max[1], floor_limit[1]))

output = {
"neuroglancerPrecomputedMin": float(min_max[0]),
"neuroglancerPrecomputedMax": float(min_max[1]),
"neuroglancerPrecomputedFloor": float(floor_limit[0]),
"neuroglancerPrecomputedLimit": float(floor_limit[1]),
"neuroglancerPrecomputedMin": str(floor(min_max[0])),
"neuroglancerPrecomputedMax": str(ceil(min_max[1])),
"neuroglancerPrecomputedFloor": str(floor(floor_limit[0])),
"neuroglancerPrecomputedLimit": str(ceil(floor_limit[1])),
}

logger.debug(f"output: {output}")
if output_json:
import json
Expand Down
12 changes: 11 additions & 1 deletion test/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

@pytest.fixture(
scope="session",
params=[sitk.sitkUInt8, sitk.sitkInt16, sitk.sitkUInt16, sitk.sitkFloat32, "uint16_uniform"],
params=[sitk.sitkUInt8, sitk.sitkInt16, sitk.sitkUInt16, sitk.sitkFloat32, "uint16_uniform", "uint8_bimodal"],
)
def image_mrc(request, tmp_path_factory):
if isinstance(request.param, str) and request.param == "uint16_uniform":
Expand All @@ -29,6 +29,16 @@ def image_mrc(request, tmp_path_factory):
a = np.linspace(0, 2**16 - 1, num=2**16, dtype="uint16").reshape(16, 64, 64)
img = sitk.GetImageFromArray(a)
img.SetSpacing([1.23, 1.23, 4.96])

elif isinstance(request.param, str) and request.param == "uint8_bimodal":

print(f"Calling image_mrc with {request.param}")
fn = f"image_mrc_{request.param.replace(' ', '_')}.mrc"

a = np.zeros([16, 16, 16], np.uint8)
a[len(a) // 2 :] = 255
img = sitk.GetImageFromArray(a)
img.SetSpacing([12.3, 12.3, 56.7])
else:
pixel_type = request.param
print(f"Calling image_mrc with {sitk.GetPixelIDValueAsString(pixel_type)}")
Expand Down
35 changes: 22 additions & 13 deletions test/test_histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,27 @@ def test_histogram_mai_help(cli_args):


@pytest.mark.parametrize(
"image_mrc,expected_min, expected_max, expected_floor, expected_limit",
"image_mrc,expected_min, expected_max, expected_floor, expected_limit, clamp",
[
(sitk.sitkUInt8, 0, 0, 0, 0),
(sitk.sitkInt16, 0, 0, 0, 0),
(sitk.sitkUInt16, 0, 0, 0, 0),
("uint16_uniform", 8191.5, 57343.5, 0, 65535),
(sitk.sitkUInt8, 0, 0, 0, 0, False),
(sitk.sitkInt16, 0, 0, 0, 0, True),
(sitk.sitkUInt16, 0, 0, 0, 0, False),
("uint16_uniform", 8191, 57344, 0, 65535, True),
("uint16_uniform", 8191, 57344, 0, 65535, False),
("uint8_bimodal", 0, 255, 0, 255, True),
("uint8_bimodal", -64, 319, 0, 255, False),
],
indirect=["image_mrc"],
)
def test_build_histogram_main(image_mrc, expected_min, expected_max, expected_floor, expected_limit):
def test_build_histogram_main(image_mrc, expected_min, expected_max, expected_floor, clamp, expected_limit):
runner = CliRunner()
output_filename = "out.json"
args = [image_mrc, "--mad", "1.5", "--output-json", output_filename]
if clamp:
args.append("--clamp")
print(args)
with runner.isolated_filesystem():
result = runner.invoke(
pytools.ng.build_histogram.main, [image_mrc, "--mad", "1.5", "--output-json", output_filename]
)
result = runner.invoke(pytools.ng.build_histogram.main, args=args)
assert not result.exception
with open(output_filename) as fp:
res = json.load(fp)
Expand All @@ -132,7 +137,11 @@ def test_build_histogram_main(image_mrc, expected_min, expected_max, expected_fl
assert "neuroglancerPrecomputedMax" in res
assert "neuroglancerPrecomputedFloor" in res
assert "neuroglancerPrecomputedLimit" in res
assert res["neuroglancerPrecomputedMin"] == expected_min
assert res["neuroglancerPrecomputedMax"] == expected_max
assert res["neuroglancerPrecomputedFloor"] == expected_floor
assert res["neuroglancerPrecomputedLimit"] == expected_limit
assert float(res["neuroglancerPrecomputedMin"]) == expected_min
assert float(res["neuroglancerPrecomputedMax"]) == expected_max
assert float(res["neuroglancerPrecomputedFloor"]) == expected_floor
assert float(res["neuroglancerPrecomputedLimit"]) == expected_limit
assert type(res["neuroglancerPrecomputedMin"]) == str
assert type(res["neuroglancerPrecomputedMax"]) == str
assert type(res["neuroglancerPrecomputedFloor"]) == str
assert type(res["neuroglancerPrecomputedLimit"]) == str
4 changes: 4 additions & 0 deletions test/test_mrc2ngpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ def test_mrc2ngpc(image_mrc, expected_pixel_type):
assert "neuroglancerPrecomputedMin" in mm
assert "neuroglancerPrecomputedFloor" in mm
assert "neuroglancerPrecomputedLimit" in mm
assert type(mm["neuroglancerPrecomputedMin"]) == str
assert type(mm["neuroglancerPrecomputedMax"]) == str
assert type(mm["neuroglancerPrecomputedFloor"]) == str
assert type(mm["neuroglancerPrecomputedLimit"]) == str