Skip to content
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
10 changes: 5 additions & 5 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ ImageReadFormatManager ← bound

It splits in two, by risk rather than by size.

**3a — geometry.** Bind `query_extents` and `query_core_extents`, each with
its two overloads, over a format manager and over a reader provider. Read-only
surface, no threads, no GIL, and useful for sizing any read rather than only a
batch. Note the provider overloads take a non-const reference where the format
manager ones take a const one.
**3a — geometry.** Bind `query_extents` and `query_core_extents` over a format
manager. Read-only surface, no threads, no GIL, and useful for sizing any read
rather than only a batch. Their overloads over a reader provider wait for 3b,
which is what binds the provider; those take a non-const reference where the
format manager ones take a const one.

`query_core_extents` returns the shape of a single image or volume, which is
what a batch destination carries beside its leading extent, so no arithmetic
Expand Down
2 changes: 1 addition & 1 deletion external/rexlib
Submodule rexlib updated 362 files
9 changes: 8 additions & 1 deletion python/rexlib/em/image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,21 @@
get_image_read_format_manager as get_image_read_format_manager,
get_image_write_format_manager as get_image_write_format_manager,
)
from ._functions import read as read, write as write
from ._functions import (
query_core_extents as query_core_extents,
query_extents as query_extents,
read as read,
write as write,
)

__all__ = [
"ImageLocation",
"ImageReadFormatManager",
"ImageWriteFormatManager",
"get_image_read_format_manager",
"get_image_write_format_manager",
"query_core_extents",
"query_extents",
"read",
"write",
]
44 changes: 44 additions & 0 deletions python/rexlib/em/image/_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,50 @@ def _resolve_write_manager(
manager = get_image_write_format_manager(get_default_catalog())
return manager

def query_extents(
path: str,
manager: ImageReadFormatManager | None = None
) -> tuple[int, ...]:
"""
Get the extents of a whole image file.

Slowest axis first, so a stack reports the axis it stacks along
before the shape of one of its images.

Args:
path: The file to ask about.
manager: The formats to recognize the file with. Defaults to the
ones the default catalog holds.

Returns:
tuple: The extents of the file.
"""
return _raw.query_extents(_resolve_read_manager(manager), path)

def query_core_extents(
path: str,
manager: ImageReadFormatManager | None = None
) -> tuple[int, ...]:
"""
Get the extents of one image or volume of a file.

The axes a file stacks along are left out, so this is the shape a
batch destination carries beside its leading extent:

descriptor = rexlib.make_contiguous_array_descriptor(
(len(locations), *query_core_extents(path)), data_type
)

Args:
path: The file to ask about.
manager: The formats to recognize the file with. Defaults to the
ones the default catalog holds.

Returns:
tuple: The extents of one image or volume.
"""
return _raw.query_core_extents(_resolve_read_manager(manager), path)

def read(
path: str | ImageLocation,
manager: ImageReadFormatManager | None = None,
Expand Down
40 changes: 40 additions & 0 deletions src/em/image/image_read_format_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,41 @@

#include <rexlib/core/service_catalog.hpp>

#include <cstddef>
#include <string>
#include <vector>

namespace rexlib
{

namespace py = pybind11;

static py::tuple to_shape(const std::vector<std::size_t> &extents)
{
py::tuple shape(extents.size());
for (std::size_t i = 0; i < extents.size(); ++i)
{
shape[i] = extents[i];
}
return shape;
}

static py::tuple query_extents(
const em::image_read_format_manager &formats,
const std::string &path
)
{
return to_shape(em::query_extents(formats, path));
}

static py::tuple query_core_extents(
const em::image_read_format_manager &formats,
const std::string &path
)
{
return to_shape(em::query_core_extents(formats, path));
}

static std::shared_ptr<em::image_read_format_manager>
get_image_read_format_manager(service_catalog &catalog)
{
Expand All @@ -22,6 +54,14 @@ declare_image_read_format_manager(pybind11::module_ &m)
void define_image_read_format_manager(pybind11::module_ &m)
{
m.def("get_image_read_format_manager", &get_image_read_format_manager);
m.def(
"query_extents", &query_extents,
py::arg("formats"), py::arg("path")
);
m.def(
"query_core_extents", &query_core_extents,
py::arg("formats"), py::arg("path")
);
}

} // namespace rexlib
76 changes: 76 additions & 0 deletions tests/em/image/test_queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# SPDX-License-Identifier: GPL-3.0-only

import pytest

import rexlib

image = rexlib.em.image

def test_reports_the_extents_it_was_written_with(__written_file):
assert image.query_extents(__written_file) == (4, 6)

def test_extents_are_a_tuple(__written_file):
assert isinstance(image.query_extents(__written_file), tuple)

def test_core_extents_are_a_tuple(__written_file):
assert isinstance(image.query_core_extents(__written_file), tuple)

def test_core_extents_are_the_trailing_extents(__written_file):
# The axes a file stacks along are left out, so whatever the rank, the
# core is the tail of the whole.
extents = image.query_extents(__written_file)
core = image.query_core_extents(__written_file)
assert len(core) <= len(extents)
assert extents[len(extents) - len(core):] == core

def test_what_it_reports_sizes_a_destination(__written_file, __setup_context):
descriptor = rexlib.make_contiguous_array_descriptor(
(3, *image.query_core_extents(__written_file)),
rexlib.NumericalType.float32
)
destination = rexlib.zeros(
descriptor, rexlib.hardware.MemoryResourceAffinity.host, __setup_context
)
assert destination.shape[1:] == image.query_core_extents(__written_file)

def test_accepts_an_explicit_manager(__written_file):
catalog = rexlib.ServiceCatalog()
manager = image.get_image_read_format_manager(catalog)
assert image.query_extents(__written_file, manager) == (4, 6)

@pytest.mark.parametrize(
"query",
[
pytest.param(image.query_extents, id="Whole file"),
pytest.param(image.query_core_extents, id="One image"),
]
)
def test_raises_on_a_file_no_format_recognizes(query):
with pytest.raises(RuntimeError):
query('/path/to/no/such/file.mrc')

@pytest.fixture
def __written_file(tmp_path, __setup_context):
path = str(tmp_path / 'stack.mrc')
descriptor = rexlib.make_contiguous_array_descriptor(
(4, 6), rexlib.NumericalType.float32
)
image.write(
rexlib.zeros(
descriptor,
rexlib.hardware.MemoryResourceAffinity.host,
__setup_context
),
path
)
return path

@pytest.fixture
def __setup_context():
catalog = rexlib.ServiceCatalog()
manager = rexlib.hardware.get_device_manager(catalog)
session = manager.create_device_session(rexlib.hardware.DeviceIndex('cpu', 0))
device_context = rexlib.hardware.DeviceContext(session)
program_manager = rexlib.dispatch.get_program_manager(catalog)
dispatcher = rexlib.dispatch.make_eager_dispatcher(program_manager)
return rexlib.dispatch.ExecutionContext(device_context, dispatcher)
Loading