Skip to content

Commit 79bca0c

Browse files
committed
ENH: Use surfio for irap
1 parent 6c36b6d commit 79bca0c

File tree

3 files changed

+71
-149
lines changed

3 files changed

+71
-149
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies = [
4949
"scipy>=1.5",
5050
"segyio>1.8.0",
5151
"shapely>=1.6.2",
52+
"surfio",
5253
"tables",
5354
"typing_extensions",
5455
"xtgeoviz",

src/xtgeo/surface/_regsurf_export.py

Lines changed: 39 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
from __future__ import annotations
44

5-
import io
65
import json
76
import struct
87
from typing import TYPE_CHECKING
98

109
import h5py
1110
import hdf5plugin
1211
import numpy as np
12+
import surfio
1313

1414
from xtgeo import _cxtgeo
1515
from xtgeo.common.constants import UNDEF_MAP_IRAPA, UNDEF_MAP_IRAPB
@@ -47,113 +47,65 @@
4747
def export_irap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
4848
"""Export to Irap RMS ascii format."""
4949

50-
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPA, order="F")
50+
vals = np.ma.filled(self.values, fill_value=UNDEF_MAP_IRAPA)
5151

5252
yinc = self.yinc * self.yflip
5353

5454
xmax = self.xori + (self.ncol - 1) * self.xinc
5555
ymax = self.yori + (self.nrow - 1) * yinc
5656

57-
header = (
58-
f"-996 {self.nrow} {self.xinc} {yinc}\n"
59-
f"{self.xori} {xmax} {self.yori} {ymax}\n"
60-
f"{self.ncol} {self.rotation} {self.xori} {self.yori}\n"
61-
"0 0 0 0 0 0 0\n"
57+
surf = surfio.IrapSurface(
58+
surfio.IrapHeader(
59+
ncol=self.ncol,
60+
nrow=self.nrow,
61+
xori=self.xori,
62+
yori=self.yori,
63+
xinc=self.xinc,
64+
yinc=yinc,
65+
xmax=xmax,
66+
ymax=ymax,
67+
rot=self.rotation,
68+
xrot=self.xori,
69+
yrot=self.yori,
70+
),
71+
values=vals,
6272
)
6373

64-
def _optimal_shape(vals, start=9):
65-
"""Optimal shape for the data.
66-
67-
It seems by reverse engineering that RMS accepts only 9 or less items per line
68-
"""
69-
# Check if divisible by a number
70-
size = len(vals)
71-
# Find the nearest factorization
72-
for i in range(start, 1, -1):
73-
if size % i == 0:
74-
return (int(size // i), int(i)) # Ensure integers are returned
75-
76-
# If we can't find a perfect divisor, return a valid shape
77-
return (int(size), 1)
78-
79-
buffer = io.StringIO()
80-
81-
np.savetxt(buffer, vals.reshape(_optimal_shape(vals)), fmt="%f", delimiter=" ")
82-
data = buffer.getvalue()
83-
84-
# Combine header and data
85-
buf = (header + data).encode("latin1")
86-
8774
if mfile.memstream:
88-
mfile.file.write(buf)
75+
mfile.file.write(surf.to_ascii_string().encode("latin1"))
8976
else:
90-
with open(mfile.name, "wb") as fout:
91-
fout.write(buf)
92-
93-
del vals
77+
surf.to_ascii_file(mfile.name)
9478

9579

9680
def export_irap_binary(self: RegularSurface, mfile: FileWrapper) -> None:
9781
"""Export to Irap RMS binary format."""
9882

99-
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPB, order="F")
83+
vals = np.ma.filled(self.values, fill_value=UNDEF_MAP_IRAPB)
10084

10185
yinc = self.yinc * self.yflip
86+
xmax = self.xori + (self.ncol - 1) * self.xinc
87+
ymax = self.yori + (self.nrow - 1) * yinc
10288

103-
header = struct.pack(
104-
">3i6f3i3f10i", # > means big endian storage
105-
32,
106-
-996,
107-
self.nrow,
108-
self.xori,
109-
self.xori + self.xinc * (self.ncol - 1),
110-
self.yori,
111-
self.yori + yinc * (self.nrow - 1),
112-
self.xinc,
113-
yinc,
114-
32,
115-
16,
116-
self.ncol,
117-
self.rotation,
118-
self.xori,
119-
self.yori,
120-
16,
121-
28,
122-
0,
123-
0,
124-
0,
125-
0,
126-
0,
127-
0,
128-
0,
129-
28,
89+
surf = surfio.IrapSurface(
90+
surfio.IrapHeader(
91+
ncol=self.ncol,
92+
nrow=self.nrow,
93+
xori=self.xori,
94+
yori=self.yori,
95+
xinc=self.xinc,
96+
yinc=yinc,
97+
xmax=xmax,
98+
ymax=ymax,
99+
rot=self.rotation,
100+
xrot=self.xori,
101+
yrot=self.yori,
102+
),
103+
values=vals,
130104
)
131-
inum = self.nrow * self.ncol
132-
133-
# export to Irap binary in ncol chunks (the only chunk size accepted by RMS)
134-
nchunk = self.ncol
135-
chunks = [nchunk] * (inum // nchunk)
136-
if (inum % nchunk) > 0:
137-
chunks.append(inum % nchunk)
138-
start = 0
139-
data = bytearray(header)
140-
chunk_size = nchunk * 4
141-
142-
# Precompute the struct.pack format for chunk size
143-
chunk_size_pack = struct.pack(">i", chunk_size)
144-
145-
for chunk in chunks:
146-
chunk_data = np.array(vals[start : start + chunk], dtype=">f4").tobytes()
147-
data.extend(chunk_size_pack)
148-
data.extend(chunk_data)
149-
data.extend(chunk_size_pack)
150-
start += chunk
151-
152105
if mfile.memstream:
153-
mfile.file.write(data)
106+
mfile.file.write(surf.to_binary_buffer())
154107
else:
155-
with open(mfile.name, "wb") as fout:
156-
fout.write(data)
108+
surf.to_binary_file(mfile.file)
157109

158110

159111
def export_ijxyz_ascii(self: RegularSurface, mfile: FileWrapper) -> None:

src/xtgeo/surface/_regsurf_import.py

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from __future__ import annotations
44

55
import json
6-
import mmap
76
from struct import unpack
87
from typing import TYPE_CHECKING
98

109
import h5py
1110
import numpy as np
11+
import surfio
1212

1313
import xtgeo.common.sys as xsys
1414
from xtgeo import _cxtgeo
@@ -50,31 +50,20 @@ def import_irap_binary(mfile: FileWrapper, values: bool = True, **_):
5050
if mfile.memstream:
5151
mfile.file.seek(0)
5252
buf = mfile.file.read()
53+
surf = surfio.IrapSurface.from_binary_buffer(buf)
5354
else:
54-
with open(mfile.file, "rb") as fhandle:
55-
buf = mmap.mmap(fhandle.fileno(), 0, access=mmap.ACCESS_READ)
56-
57-
# Ensure buffer is large enough for header
58-
header_size = 100
59-
if len(buf) < header_size:
60-
raise ValueError("Buffer size is too small for header")
61-
62-
# unpack header with big-endian format string (cf. docstring info)
63-
hed = np.frombuffer(
64-
buf[:header_size],
65-
dtype=">i4,>i4,>i4,>f4,>f4,>f4,>f4,>f4,>f4,>i4," # <32> IDFLAG NY XORI ... <32>
66-
+ ">i4,>i4,>f4,>f4,>f4,>i4," # <16> NX ROT X0ORI Y0ORI<16>
67-
+ ">i4,>i4,>i4,>i4,>i4,>i4,>i4,>i4,>i4", # <28> 0 0 0 0 0 0 0 <28>
68-
)
55+
surf = surfio.IrapSurface.from_binary_file(mfile.file)
56+
57+
header = surf.header
6958

7059
args = {}
71-
args["nrow"] = int(hed[0][2])
72-
args["xori"] = float(hed[0][3])
73-
args["yori"] = float(hed[0][5])
74-
args["xinc"] = float(hed[0][7])
75-
args["yinc"] = float(hed[0][8])
76-
args["ncol"] = int(hed[0][11])
77-
args["rotation"] = float(hed[0][12])
60+
args["nrow"] = header.nrow
61+
args["xori"] = header.xori
62+
args["yori"] = header.yori
63+
args["xinc"] = header.xinc
64+
args["yinc"] = header.yinc
65+
args["ncol"] = header.ncol
66+
args["rotation"] = header.rot
7867

7968
args["yflip"] = 1
8069
if args["yinc"] < 0.0:
@@ -84,32 +73,15 @@ def import_irap_binary(mfile: FileWrapper, values: bool = True, **_):
8473
if not values:
8574
return args
8675

87-
# Values: traverse through data blocks
88-
stv = header_size # Starting byte
89-
datav = []
90-
91-
while stv < len(buf):
92-
# start block integer - number of bytes of floats in following block
93-
blockv = np.frombuffer(buf[stv : stv + 4], dtype=">i4")[0]
94-
stv += 4
95-
# floats
96-
datav.append(np.frombuffer(buf[stv : stv + blockv], dtype=">f4"))
97-
stv += blockv
98-
# end block integer not needed really
99-
stv += 4
100-
101-
values = np.hstack(datav)
102-
values = np.reshape(values, (args["ncol"], args["nrow"]), order="F")
103-
values = np.array(values, order="C")
76+
values = np.ascontiguousarray(surf.values)
10477
values = np.ma.masked_greater_equal(values, UNDEF_MAP_IRAPB)
10578
args["values"] = np.ma.masked_invalid(values)
10679

107-
del buf
10880
return args
10981

11082

11183
def import_irap_ascii(mfile: FileWrapper, **_):
112-
"""Import Irap in pure python code, suitable for memstreams, and now efficient.
84+
"""Import Irap ascii which has the following format:
11385
-996 2010 5.000000 5.000000
11486
461587.553724 467902.553724 5927061.430176 5937106.430176
11587
1264 30.000011 461587.553724 5927061.430176
@@ -124,33 +96,30 @@ def import_irap_ascii(mfile: FileWrapper, **_):
12496
if mfile.memstream:
12597
mfile.file.seek(0)
12698
buf = mfile.file.read().decode()
99+
surface = surfio.IrapSurface.from_ascii_string(buf)
100+
del buf
127101
else:
128-
with open(mfile.file) as fhandle:
129-
buf = fhandle.read()
130-
131-
buf = buf.split(maxsplit=19)
132-
args = {}
133-
args["nrow"] = int(buf[1])
134-
args["xinc"] = float(buf[2])
135-
args["yinc"] = float(buf[3])
136-
args["xori"] = float(buf[4])
137-
args["yori"] = float(buf[6])
138-
args["ncol"] = int(buf[8])
139-
args["rotation"] = float(buf[9])
140-
141-
nvalues = args["nrow"] * args["ncol"]
142-
values = np.fromstring(buf[19], dtype=np.double, count=nvalues, sep=" ")
143-
144-
values = np.reshape(values, (args["ncol"], args["nrow"]), order="F")
145-
values = np.array(values, order="C")
146-
args["values"] = np.ma.masked_greater_equal(values, UNDEF_MAP_IRAPA)
102+
surface = surfio.IrapSurface.from_ascii_file(str(mfile.file))
103+
104+
values = np.ascontiguousarray(surface.values)
105+
values = np.ma.masked_greater_equal(values, UNDEF_MAP_IRAPA)
106+
values = np.ma.masked_invalid(values)
107+
args = {
108+
"nrow": surface.header.nrow,
109+
"xinc": surface.header.xinc,
110+
"yinc": surface.header.yinc,
111+
"xori": surface.header.xori,
112+
"yori": surface.header.yori,
113+
"ncol": surface.header.ncol,
114+
"rotation": surface.header.rot,
115+
"values": values,
116+
}
147117

148118
args["yflip"] = 1
149119
if args["yinc"] < 0.0:
150120
args["yinc"] *= -1
151121
args["yflip"] = -1
152122

153-
del buf
154123
return args
155124

156125

0 commit comments

Comments
 (0)