pnm 1.0.3
peclet.pnm — pore-network extraction from SDF geometry (Kokkos)
Loading...
Searching...
No Matches
sdf_reader.cpp
Go to the documentation of this file.
1
3#include "sdf_reader.h"
4
5#include <algorithm>
6#include <cstring>
7#include <filesystem>
8#include <fstream>
9#include <sstream>
10#include <stdexcept>
11
12// A simple string helper to find value of an XML attribute
13// e.g. WholeExtent="0 10 0 10 0 10"
14std::string get_attribute_value(const std::string &content, const std::string &attr_name) {
15 size_t pos = content.find(attr_name + "=\"");
16 if (pos == std::string::npos)
17 return "";
18
19 size_t start = pos + attr_name.length() + 2;
20 size_t end = content.find("\"", start);
21 if (end == std::string::npos)
22 return "";
23
24 return content.substr(start, end - start);
25}
26
27SDFData SDFReader::read_vti(const std::string &filename) {
28 std::ifstream file(filename, std::ios::binary);
29 if (!file.is_open()) {
30 throw std::runtime_error("Could not open file: " + filename);
31 }
32
33 SDFData data{};
34
35 // Read file content line by line until we find AppendedData or similar
36 // We assume the header is small enough to read comfortably
37 // BUT we need to be careful not to read the binary part as text
38 // Strategy: Read chunks or just locate the "<AppendedData encoding=\"raw\">"
39 // tag.
40
41 // A clearer approach for mixed text/binary:
42 // Read the file into a string buffer until we see the start of binary marker.
43 // The user said: "Appended Raw Binary".
44 // Usually VTK VTI structure is:
45 // <VTKFile ...> ... <AppendedData
46 // encoding="raw">_LENGTH_DATA...</AppendedData></VTKFile>
47
48 // We will scan for the underscore `_` identifying the start of raw data,
49 // but we must first parse the header info which appears before.
50
51 std::string header_buffer;
52 char c;
53 bool found_marker = false;
54
55 // We'll read until we see the sequence `_` immediately following
56 // "<AppendedData encoding=\"raw\">" Actually, often it's just
57 // `...<AppendedData encoding="raw">` then newline then `_`. Let's read the
58 // first 4KB, it should contain the header.
59
60 // Better: Read whole file into memory? No, could be large.
61 // Let's read until "</InAppendedData>" is not useful because it's at the end.
62 // We search for the offset=0 in the AppendedData.
63
64 // Implementation:
65 // 1. Read header text until `header_type="UInt64"` (verification) or just
66 // parse specific tags.
67 // 2. Find "WholeExtent", "Origin", "Spacing".
68
69 // Let's read a chunk (e.g. 8KB) to parse XML.
70 std::vector<char> buffer(8192);
71 file.read(buffer.data(), buffer.size());
72 std::string header(buffer.data(), file.gcount());
73
74 // Parse WholeExtent
75 std::string extent_str = get_attribute_value(header, "WholeExtent");
76 if (extent_str.empty())
77 throw std::runtime_error("Could not find WholeExtent in VTI header");
78
79 int x1, x2, y1, y2, z1, z2;
80 std::stringstream ss_ext(extent_str);
81 ss_ext >> x1 >> x2 >> y1 >> y2 >> z1 >> z2;
82 data.resolution = {x2 - x1 + 1, y2 - y1 + 1, z2 - z1 + 1};
83
84 // Parse Origin
85 std::string origin_str = get_attribute_value(header, "Origin");
86 std::stringstream ss_org(origin_str);
87 ss_org >> data.origin[0] >> data.origin[1] >> data.origin[2];
88
89 // Parse Spacing
90 std::string spacing_str = get_attribute_value(header, "Spacing");
91 std::stringstream ss_spc(spacing_str);
92 ss_spc >> data.spacing[0] >> data.spacing[1] >> data.spacing[2];
93
94 // Find binary data start
95 // Look for "<AppendedData encoding=\"raw\">"
96 std::string appended_tag = "<AppendedData encoding=\"raw\">";
97 size_t tag_pos = header.find(appended_tag);
98 if (tag_pos == std::string::npos)
99 throw std::runtime_error("Could not find AppendedData tag");
100
101 // The binary data starts after the tag. It typically starts with an
102 // underscore `_` followed by the length (UInt64). Let's search for `_` after
103 // the tag.
104 size_t underscore_pos = header.find("_", tag_pos);
105 if (underscore_pos == std::string::npos) {
106 // Maybe we didn't read enough?
107 // If the header is larger than 8KB, we are in trouble.
108 // Realistically headers are small.
109 throw std::runtime_error("Could not find binary data marker '_' in first 8KB");
110 }
111
112 // Seek file to the position of the length (immediately after `_`)
113 file.clear(); // Clear EOF flag if we read past end in buffer
114 file.seekg(underscore_pos + 1, std::ios::beg);
115
116 // Read length (UInt64)
117 uint64_t data_bytes = 0;
118 file.read(reinterpret_cast<char *>(&data_bytes), sizeof(uint64_t));
119
120 // Check for read failure
121 if (!file) {
122 throw std::runtime_error("Failed to read data_bytes (UInt64) from file.");
123 }
124
125 // The appended block must hold exactly one Float32 value per voxel of WholeExtent: anything
126 // else (a Float64/Int array, zlib-compressed appended data, a second array, a truncated file)
127 // would be read as garbage, so it is an error, not a warning.
128 const size_t expected_bytes = data.size() * sizeof(float);
129 if (data_bytes != expected_bytes) {
130 throw std::runtime_error("VTI appended data length (" + std::to_string(data_bytes) +
131 " bytes) does not match WholeExtent x Float32 (" +
132 std::to_string(expected_bytes) + " bytes) in " + filename +
133 ": expected one uncompressed Float32 array (raw appended encoding)");
134 }
135
136 data.sdf_values.resize(data.size());
137 file.read(reinterpret_cast<char *>(data.sdf_values.data()), data_bytes);
138 if (file.gcount() != static_cast<std::streamsize>(data_bytes)) {
139 throw std::runtime_error("VTI file " + filename + " is truncated: read " +
140 std::to_string(file.gcount()) + " of " + std::to_string(data_bytes) +
141 " data bytes");
142 }
143
144 return data;
145}
static SDFData read_vti(const std::string &filename)
std::string get_attribute_value(const std::string &content, const std::string &attr_name)
Reader for SDF / VTI geometry (interface).