Skip to content

Commit 8a13b2e

Browse files
committed
ENH: Use surfio for irap ascii
1 parent 6c36b6d commit 8a13b2e

File tree

3 files changed

+74
-150
lines changed

3 files changed

+74
-150
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: 42 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from xtgeo.common.constants import UNDEF_MAP_IRAPA, UNDEF_MAP_IRAPB
1616
from xtgeo.common.log import null_logger
1717

18+
import surfio
19+
1820
if TYPE_CHECKING:
1921
from xtgeo.io._file import FileWrapper
2022
from xtgeo.surface.regular_surface import RegularSurface
@@ -47,113 +49,63 @@
4749
def export_irap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
4850
"""Export to Irap RMS ascii format."""
4951

50-
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPA, order="F")
51-
5252
yinc = self.yinc * self.yflip
5353

5454
xmax = self.xori + (self.ncol - 1) * self.xinc
5555
ymax = self.yori + (self.nrow - 1) * yinc
56-
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"
56+
values = np.ma.filled(self.values, fill_value=UNDEF_MAP_IRAPB)
57+
58+
surf = surfio.IrapSurface(
59+
surfio.IrapHeader(
60+
ncol=self.ncol,
61+
nrow=self.nrow,
62+
xori=self.xori,
63+
yori=self.yori,
64+
xinc=self.xinc,
65+
yinc=yinc,
66+
xmax=xmax,
67+
ymax=ymax,
68+
rot=self.rotation,
69+
xrot=self.xori,
70+
yrot=self.yori,
71+
),
72+
values=values,
6273
)
6374

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-
8775
if mfile.memstream:
88-
mfile.file.write(buf)
76+
mfile.file.write(surf.export_ascii().encode("latin1"))
8977
else:
90-
with open(mfile.name, "wb") as fout:
91-
fout.write(buf)
92-
93-
del vals
78+
surf.export_ascii_file(mfile.name)
9479

9580

9681
def export_irap_binary(self: RegularSurface, mfile: FileWrapper) -> None:
9782
"""Export to Irap RMS binary format."""
9883

99-
vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPB, order="F")
100-
10184
yinc = self.yinc * self.yflip
102-
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,
85+
xmax = self.xori + (self.ncol - 1) * self.xinc
86+
ymax = self.yori + (self.nrow - 1) * yinc
87+
values = np.ma.filled(self.values, fill_value=UNDEF_MAP_IRAPB)
88+
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=values,
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 & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from xtgeo.common.xtgeo_dialog import XTGeoDialog
1818
from xtgeo.metadata.metadata import MetaDataRegularSurface
1919

20+
import surfio
21+
2022
from ._regsurf_ijxyz_parser import parse_ijxyz
2123
from ._zmap_parser import parse_zmap
2224

@@ -50,31 +52,20 @@ def import_irap_binary(mfile: FileWrapper, values: bool = True, **_):
5052
if mfile.memstream:
5153
mfile.file.seek(0)
5254
buf = mfile.file.read()
55+
surf = surfio.IrapSurface.from_binary_buffer(buf)
5356
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-
)
57+
surf = surfio.IrapSurface.from_binary_file(mfile.file)
58+
59+
header = surf.header
6960

7061
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])
62+
args["nrow"] = header.nrow
63+
args["xori"] = header.xori
64+
args["yori"] = header.yori
65+
args["xinc"] = header.xinc
66+
args["yinc"] = header.yinc
67+
args["ncol"] = header.ncol
68+
args["rotation"] = header.rot
7869

7970
args["yflip"] = 1
8071
if args["yinc"] < 0.0:
@@ -84,32 +75,15 @@ def import_irap_binary(mfile: FileWrapper, values: bool = True, **_):
8475
if not values:
8576
return args
8677

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")
78+
values = surf.values
10479
values = np.ma.masked_greater_equal(values, UNDEF_MAP_IRAPB)
10580
args["values"] = np.ma.masked_invalid(values)
10681

107-
del buf
10882
return args
10983

11084

11185
def import_irap_ascii(mfile: FileWrapper, **_):
112-
"""Import Irap in pure python code, suitable for memstreams, and now efficient.
86+
"""Import Irap ascii which has the following format:
11387
-996 2010 5.000000 5.000000
11488
461587.553724 467902.553724 5927061.430176 5937106.430176
11589
1264 30.000011 461587.553724 5927061.430176
@@ -124,33 +98,30 @@ def import_irap_ascii(mfile: FileWrapper, **_):
12498
if mfile.memstream:
12599
mfile.file.seek(0)
126100
buf = mfile.file.read().decode()
101+
surface = surfio.IrapSurface.from_ascii_string(buf)
102+
del buf
127103
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=" ")
104+
surface = surfio.IrapSurface.from_ascii_file(str(mfile.file))
143105

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)
106+
values = surface.values
107+
values = np.ma.masked_greater_equal(values, UNDEF_MAP_IRAPB)
108+
values = np.ma.masked_invalid(values)
109+
args = {
110+
"nrow": surface.header.ny,
111+
"xinc": surface.header.xinc,
112+
"yinc": surface.header.yinc,
113+
"xori": surface.header.xori,
114+
"yori": surface.header.yori,
115+
"ncol": surface.header.nx,
116+
"rotation": surface.header.rot,
117+
"values": surface.values,
118+
}
147119

148120
args["yflip"] = 1
149121
if args["yinc"] < 0.0:
150122
args["yinc"] *= -1
151123
args["yflip"] = -1
152124

153-
del buf
154125
return args
155126

156127

0 commit comments

Comments
 (0)