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

Updated block models for v2 #125

Open
wants to merge 45 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a0a7029
Ignore vscode settings.
Feb 26, 2023
2a864bf
Fix test failures with latest NumPy.
Feb 26, 2023
fa89c46
Sketched out the basics of new block models.
Feb 27, 2023
01a856d
Moved to sub-package.
Feb 27, 2023
8448b16
Split up.
Feb 27, 2023
3c2f800
Added more sub-block checks.
Feb 28, 2023
1a96190
Split definitions from models to make things simpler.
Feb 28, 2023
8f3335d
Test that uint array packing works.
Feb 28, 2023
e2c7ab6
Raise ValidationError from sub-block checks.
Feb 28, 2023
3a8901b
Added free-form sub-blocked models.
Feb 28, 2023
6726a95
Finished sub-block checks and tests for them.
Mar 1, 2023
002b963
Removed dead test code, added a few more tests.
Mar 1, 2023
d2e6850
Cleaned up code and added docstrings.
Mar 2, 2023
ceb94d4
Merge remote-tracking branch 'origin/master' into block-models-v2
Mar 2, 2023
f00f3ba
Cleaned up merge issues.
Mar 2, 2023
90243cd
Added schemas to objects without them, and cleaned up code.
Mar 2, 2023
8f3b495
Fixed pylint complaints.
Mar 2, 2023
04fbe72
Fixed failing schema lookups giving bad error messages.
Mar 2, 2023
e4a148f
Fixed v2 asset test.
Mar 2, 2023
2d288c4
Copy origin correctly for VolumeGridGeometry.
Mar 2, 2023
63cc16b
Removed match statement.
Mar 2, 2023
e7f8e07
Don't assert things that are different on Linux.
Mar 2, 2023
b75a653
Use the lower-memory version of numpy.isin.
Mar 2, 2023
6385672
Updated docs and fixed tests.
Mar 2, 2023
5a3009e
Fixed remaining tests.
Mar 2, 2023
14df011
Remove unused import.
Mar 2, 2023
67eac27
Removed dead code and outdated notebooks.
Mar 3, 2023
b65d863
Code review fixes.
Mar 3, 2023
4d27671
Code review changes and some tidying up.
Mar 13, 2023
93bd53b
Fixed unions and improved docs a little.
Mar 14, 2023
f7c8b5c
Added missing doc link.
Mar 14, 2023
15c5925
Removed unnecessary methods.
Mar 14, 2023
e8f4aa4
Changed to a single top-level block model element with optional sub-b…
Mar 14, 2023
231d7c2
Moved the common parts of the block model definitions back to the blo…
Mar 14, 2023
b009389
Cleaned up sub-block modes.
Mar 14, 2023
2a67566
Removed useless code, cleaned up, and improved documentation.
Mar 16, 2023
1a1b19e
Added missing part of sub-block docs.
Mar 16, 2023
56adf6a
Fixed pylint errors.
Mar 16, 2023
6da9dac
Fixed error in Python 3.11.
Mar 16, 2023
2476ea8
Improved free-form docs.
Mar 17, 2023
99db672
Fixed docs on BlockModel.
Mar 17, 2023
a353b9a
Merge pull request #2 from seequent/block-models-v2
Tim-Evans-Seequent Mar 17, 2023
05b7d63
Added examples of writing and reading block models.
Mar 22, 2023
0dd0ad4
Merge pull request #3 from seequent/block-model-examples
Tim-Evans-Seequent Mar 22, 2023
3828402
Improved validation of tensor arrays and removed empty file.
Mar 23, 2023
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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ cover/
example.omf
example.png
testdata/

# VSCode project settings
.vscode/

# Example outputs
examples/*.omf
examples/*.csv
Binary file modified assets/v2/test_file.omf
Binary file not shown.
41 changes: 32 additions & 9 deletions docs/content/blockmodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,45 @@ Block Models
Element
-------

The `BlockModel` element is used to store all types of block model. Different types
of block model can be described using the `grid` and `subblocks` properties.

.. autoclass:: omf.blockmodel.BlockModel

Block Model Grid
----------------

The blocks of a block model can lie on either a regular or tensor grid. For sub-blocked
models this only applies to the parent blocks.

.. autoclass:: omf.blockmodel.RegularGrid

.. autoclass:: omf.blockmodel.TensorGrid

.. image:: /images/VolumeGridGeometry.png
:width: 80%
:align: center

.. autoclass:: omf.blockmodel.TensorGridBlockModel

.. autoclass:: omf.blockmodel.RegularBlockModel

.. autoclass:: omf.blockmodel.RegularSubBlockModel
Sub-blocks
----------

.. autoclass:: omf.blockmodel.OctreeSubBlockModel
.. autoclass:: omf.blockmodel.RegularSubblocks

.. autoclass:: omf.blockmodel.ArbitrarySubBlockModel
.. autoclass:: omf.blockmodel.FreeformSubblocks

Attributes
----------

Attributes is a list of :ref:`attributes <attributes>`. For block models,
:code:`location='parent_blocks'` and :code:`location='sub_blocks'` are valid.
Attributes is a list of :ref:`attributes <attributes>`.

For block models :code:`location='parent_blocks'`, or the backward compatible
:code:`location='cells'`, places attribute values on the parent blocks. There must be a
value for each parent block and ordering is such that as you move down the attribute
array the U index increases fastest, then V, and finally W.

Using :code:`location='vertices'` instead puts the attribute values on the parent block
corners. The ordering is the same.

Sub-blocked models can still have attributes on their parent blocks using the above modes,
or on the sub-blocks using :code:`location='subblocks'`. For sub-blocks the ordering
matches the `corners` array.
12 changes: 7 additions & 5 deletions docs/content/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Also, this example builds elements all at once. They can also be initialized
with no arguments, and properties can be set one-by-one (see code snippet at
bottom of page).

.. code:: python
.. code-block:: python
:name: test_doc

import datetime
Expand Down Expand Up @@ -141,12 +141,14 @@ bottom of page).
),
],
)
vol = omf.TensorGridBlockModel(
vol = omf.blockmodel.BlockModel(
name="vol",
tensor_u=np.ones(10).astype(float),
tensor_v=np.ones(15).astype(float),
tensor_w=np.ones(20).astype(float),
origin=[10.0, 10.0, -10],
grid=omf.blockmodel.TensorGrid(
tensor_u=np.ones(10, dtype=float),
tensor_v=np.ones(15, dtype=float),
tensor_w=np.ones(20, dtype=float),
),
attributes=[
omf.NumericAttribute(
name="random attr", location="cells", array=np.random.rand(10 * 15 * 20)
Expand Down
133 changes: 133 additions & 0 deletions examples/octree_subblocked_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import numpy as np
import omf


def write():
# This writes an OMF file containing a small octree sub-blocked model with a few example attributes.
# Only one of the parent blocks contains sub-blocks.
model = omf.blockmodel.BlockModel(
name="Block Model",
description="A regular block model with a couple of attributes.",
origin=(100.0, 200.0, 50.0),
grid=omf.blockmodel.RegularGrid(block_count=(2, 2, 1), block_size=(10.0, 10.0, 5.0)),
subblocks=omf.blockmodel.RegularSubblocks(
mode="octree",
subblock_count=(4, 4, 2),
parent_indices=np.array([(0, 0, 0)] * 11 + [(1, 0, 0), (0, 1, 0), (1, 1, 0)]),
corners=np.array(
[
# size 1, 1, 1
(0, 0, 0, 1, 1, 1),
(1, 0, 0, 2, 1, 1),
(1, 1, 0, 2, 2, 1),
(0, 1, 0, 1, 2, 1),
# size 2, 2, 1
(2, 0, 0, 4, 2, 1),
(2, 2, 0, 4, 4, 1),
(0, 2, 0, 2, 4, 1),
(0, 0, 1, 2, 2, 2),
(2, 0, 1, 4, 2, 2),
(2, 2, 1, 4, 4, 2),
(0, 2, 1, 2, 4, 2),
# size 4, 4, 2
(0, 0, 0, 4, 4, 2),
(0, 0, 0, 4, 4, 2),
(0, 0, 0, 4, 4, 2),
]
),
),
)
model.attributes.append(
omf.NumericAttribute(
name="Number",
description="From 0.0 to 1.0",
location="subblocks",
array=np.arange(14.0) / 13.0,
)
)
model.attributes.append(
omf.CategoryAttribute(
name="Category",
description="Checkerboard categories on parent blocks",
location="parent_blocks",
array=np.array([0, 1, 1, 0]),
categories=omf.CategoryColormap(
indices=[0, 1],
values=["White", "Red"],
colors=[(255, 255, 255), (255, 0, 0)],
),
)
)
strings = []
for i0, j0, k0, i1, j1, k1 in model.subblocks.corners.array:
strings.append(f"{i1 - i0} by {j1 - j0} by {k1 - k0}")
model.attributes.append(
omf.StringAttribute(
name="Strings",
description="Gives the block shape",
location="subblocks",
array=strings,
)
)
project = omf.Project()
project.metadata["comment"] = "An OMF file containing an octree sub-blocked model."
project.elements.append(model)
omf.fileio.save(project, "octree_subblocked_model.omf", mode="w")


def _subblock_centroid_and_size(model, corners, i, j, k):
min_corner = corners[:3]
max_corner = corners[3:]
# Calculate centre and size within the [0, 1] range of the parent block.
centre = (min_corner + max_corner) / model.subblocks.subblock_count / 2
size = (max_corner - min_corner) / model.subblocks.subblock_count
# Transform to object space.
subblock_centroid = (
model.origin
+ model.axis_u * model.grid.block_size[0] * (i + centre[0])
+ model.axis_v * model.grid.block_size[1] * (j + centre[1])
+ model.axis_w * model.grid.block_size[2] * (k + centre[2])
)
subblock_size = size * model.grid.block_size
return subblock_centroid, subblock_size


def read():
# Reads the OMF file written above and converts it into a CSV file. Category colour data
# is discarded because block model CSV files don't typically store it.
project = omf.fileio.load("octree_subblocked_model.omf")
model = project.elements[0]
assert isinstance(model, omf.blockmodel.BlockModel)
names = []
data = []
for attr in model.attributes:
if isinstance(attr, omf.CategoryAttribute):
map = {index: string for index, string in zip(attr.categories.indices, attr.categories.values)}
to_string = map.get
else:
to_string = str
names.append(attr.name)
data.append((attr.array, to_string, attr.location == "parent_blocks"))
with open("octree_subblocked_model.csv", "w") as f:
f.write(f"# {model.name}\n")
f.write(f"# {model.description}\n")
f.write(f"# origin = {model.origin}\n")
f.write(f"# block size = {model.grid.block_size}\n")
f.write(f"# block count = {model.grid.block_count}\n")
f.write(f"# sub-block count = {model.subblocks.subblock_count}\n")
f.write(f"x,y,z,dx,dy,dz,{','.join(names)}\n")
for subblock_index, ((i, j, k), corners) in enumerate(
zip(model.subblocks.parent_indices.array, model.subblocks.corners.array)
):
parent_index = model.ijk_to_index((i, j, k))
centroid, size = _subblock_centroid_and_size(model, corners, i, j, k)
f.write(f"{centroid[0]},{centroid[1]},{centroid[2]},{size[0]},{size[1]},{size[2]}")
for array, to_string, on_parent in data:
f.write(",")
f.write(to_string(array[parent_index if on_parent else subblock_index]))
f.write("\n")


if __name__ == "__main__":
write()
read()
95 changes: 95 additions & 0 deletions examples/regular_block_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import numpy as np
import omf


def write():
# This writes an OMF file containing a small regular block model with a few example attributes.
model = omf.blockmodel.BlockModel(
name="Block Model",
description="A regular block model with a couple of attributes.",
origin=(100.0, 200.0, 50.0),
grid=omf.blockmodel.RegularGrid(block_count=(5, 5, 5), block_size=(10.0, 10.0, 5.0)),
)
model.attributes.append(
omf.NumericAttribute(
name="Number",
description="From 0.0 to 1.0",
location="parent_blocks",
array=np.arange(125.0) / 124.0,
)
)
model.attributes.append(
omf.CategoryAttribute(
name="Category",
description="Checkerboard categories",
location="parent_blocks",
array=np.tile(np.array((0, 1)), 63)[:-1],
categories=omf.CategoryColormap(
indices=[0, 1],
values=["White", "Red"],
colors=[(255, 255, 255), (255, 0, 0)],
),
)
)
strings = []
for i in range(5):
strings += [f"Layer {i + 1}"] * 25
model.attributes.append(
omf.StringAttribute(
name="Strings",
description="Gives the layer name",
location="parent_blocks",
array=strings,
)
)
project = omf.Project()
project.metadata["comment"] = "An OMF file containing a regular block model."
project.elements.append(model)
omf.fileio.save(project, "regular_block_model.omf", mode="w")


def read():
# Reads the OMF file written above and converts it into a CSV file. Category colour data
# is discarded because block model CSV files don't typically store it.
project = omf.fileio.load("regular_block_model.omf")
model = project.elements[0]
assert isinstance(model, omf.blockmodel.BlockModel)
sizes = ",".join(str(s) for s in model.grid.block_size)
names = []
data = []
for attr in model.attributes:
if isinstance(attr, omf.CategoryAttribute):
map = {index: string for index, string in zip(attr.categories.indices, attr.categories.values)}
to_string = map.get
else:
to_string = str
names.append(attr.name)
data.append((attr.array, to_string))
with open("regular_block_model.csv", "w") as f:
f.write(f"# {model.name}\n")
f.write(f"# {model.description}\n")
f.write(f"# origin = {model.origin}\n")
f.write(f"# block size = {model.grid.block_size}\n")
f.write(f"# block count = {model.grid.block_count}\n")
f.write(f"x,y,z,dx,dy,dz,{','.join(names)}\n")
index = 0
for k in range(model.grid.block_count[2]):
for j in range(model.grid.block_count[1]):
for i in range(model.grid.block_count[0]):
x, y, z = (
model.origin
+ model.axis_u * model.grid.block_size[0] * (i + 0.5)
+ model.axis_v * model.grid.block_size[1] * (j + 0.5)
+ model.axis_w * model.grid.block_size[2] * (k + 0.5)
)
f.write(f"{x},{y},{z},{sizes}")
for array, to_string in data:
f.write(",")
f.write(to_string(array[index]))
f.write("\n")
index += 1


if __name__ == "__main__":
write()
read()
Loading