Commit a783447c authored by Paal Kvamme's avatar Paal Kvamme
Browse files

Merge branch 'kvamme62/update-existing' into 'master'

Allow opening an existing ZGY file for writing.

See merge request !45
parents 5a081fb7 63a12d48
Pipeline #29132 passed with stages
in 6 minutes and 2 seconds
......@@ -1298,6 +1298,127 @@ IZgyWriter::open(const ZgyWriterArgs& args)
return std::shared_ptr<IZgyWriter>(new Impl::ZgySafeWriter(unsafe));
}
/**
* \brief Open an existing ZGY file for writing.
*
* \details
* There are several restrictions on using this feature.
* Some of those might be relaxed in the future.
*
* \li size, bricksize, datatype, and datarange are not allowed to change.
*
* \li filename, iocontext, compressor, and lodcompressor are not stored
* in the file itself. So the caller needs to re-specify them.
*
* \li ilstart, ilinc, xlstart, xlinc, zstart, zinc, zunit, hunit, corners
* can all be changed but note that this can only be done in the
* actual call to reopen().
*
* \li The operation is expensive, even when the only change is to
* update some metadata. The current implementation will delete
* and re-create the file behind the scenes.
*
* \li The file to be opened cannot contain any bulk data. The only
* exception is that the entire file may have been initialized to some
* arbitrary constant.
*
* \li If the file to be updated is stored on a cloud back-end then it must
* have been created by OpenZGY and written directly to the cloud.
* The reason: Even though the ZGY-Public and OpenZGY file formats are
* identical, the data on the cloud can be split into multiple chunks
* which are logically treated as a single byte stream. The choice
* of where to split differs between OpenZGY, ZGY-Public, and uploads
* done by sdutil. This restriction is currently not active but it will
* become active when a better implementation (removing most of the
* other rules) has been coded.
*
* \li If the file does contain bulk data (which currently isn't allowed)
* then it is strongly advised to not change from compressed to
* uncompressed or vice versa. Even though this might not be checked.
* If you ignore this advice then he new compression mode will only
* apply to subsequent writes.
*
* \li If the file does contain bulk data (which currently isn't allowed)
* then it is strongly advised to not update any already existing data.
* Including overwrite due to a read/modify/write cycle. With a cloud
* backend this could waste storage and might also be refused at runtime.
*
* \li A file that has never been written to may end up as a file that
* has been explicitly written as the default (usually zero) value.
* The OpenZGY api hides that distinction anyway. So it shouldn't matter.
*
* \li The operation can leave the file unusable or even deleted if some
* error occurs. Since we are creating a brand new file it would be
* possible to create this with a new name and only rename it and
* discard the original once we know that the new file is good.
* But, (a) rename on the cloud is difficult and (b) once this code
* gets implemented properly that trick isn't possible anyway.
*/
std::shared_ptr<IZgyWriter>
IZgyWriter::reopen(const ZgyWriterArgs& args)
{
// Get all existing information about the file. Including the value
// of all samples, if and only if all the samples are the same.
std::shared_ptr<IZgyReader> reader = IZgyReader::open(args._filename, args._iocontext);
ZgyWriterArgs newargs = ZgyWriterArgs()
.metafrom(reader)
.filename(args._filename);
const ZgyWriterArgs oldargs = newargs;
std::pair<bool,double> value =
reader->readconst(size3i_t{0,0,0}, reader->size(), 0, true);
reader->close();
reader.reset();
if (!value.first)
throw Errors::ZgyUserError("Cannot re-open a file that already contains data.");
// Now create a new file with the same name, making it look like we
// have just opened the old file for update. At the file back-end
// level the file is either deleted and then re-created or it is
// truncated. Either way all the information in the file is lost. At
// the ZgyWriter level the metadata is initialized to what we
// salvaged from the original. Which makes this appear to be an
// update operation. As for the bulk data, we only copy that if all
// the samples had the same value. If the input had never been
// written the new file will be explicitly written as the default
// (usually zero) value. This should not make any difference because
// OpenZGY hides that distinction anyway.
newargs
.merge(args)
.filename(args._filename)
.iocontext(args._iocontext)
.compressor(args._compressor)
.lodcompressor(args._lodcompressor);
//std::cout << "Arguents passed to function:\n";
//args.dump(std::cout);
//std::cout << "Read frim the existing file:\n";
//oldargs.dump(std::cout);
//std::cout << "To be used to create new file:\n";
//newargs.dump(std::cout);
// Since the current implementation creates a completely new empty file
// I could in fact have allowed changing these supposedly immutable
// properties. But code using that feature would break after a proper
// implementation is done. So, better to raise an error.
if (newargs._size != oldargs._size)
throw Errors::ZgyUserError("Cannot change the size of a ZGY file");
if (newargs._bricksize != oldargs._bricksize)
throw Errors::ZgyUserError("Cannot change the brick size of a ZGY file");
if (newargs._datatype != oldargs._datatype)
throw Errors::ZgyUserError("Cannot change the data type of a ZGY file");
if (newargs._datarange != oldargs._datarange)
throw Errors::ZgyUserError("Cannot change the data range of a ZGY file");
std::shared_ptr<IZgyWriter> writer = IZgyWriter::open(newargs);
// Cast is ok because all supported data types (int8, int16, float)
// can be cast to a single precision float without loss of accuracy.
const float floatval = static_cast<float>(value.second);
writer->writeconst(size3i_t{0,0,0}, writer->size(), &floatval);
// At this point it ought to look like an existing file was opened for update.
return writer;
}
std::shared_ptr<IZgyUtils>
IZgyUtils::utils(const std::string& prefix, const IOContext* iocontext)
{
......@@ -1324,6 +1445,38 @@ ZgyWriterArgs::metafrom(const std::shared_ptr<OpenZGY::IZgyReader>& reader)
return *this;
}
ZgyWriterArgs&
ZgyWriterArgs::merge(const ZgyWriterArgs& other)
{
if (other._have_size)
size(other._size[0], other._size[1], other._size[2]);
if (other._have_bricksize)
bricksize(other._bricksize[0], other._bricksize[1], other._bricksize[2]);
if (other._have_datatype)
datatype(other._datatype);
if (other._have_datarange)
datarange(other._datarange[0], other._datarange[1]);
if (other._have_zunit)
zunit(other._zunitdim, other._zunitname, other._zunitfactor);
if (other._have_hunit)
hunit(other._hunitdim, other._hunitname, other._hunitfactor);
if (other._have_ilstart)
ilstart(other._annotstart[0]);
if (other._have_ilinc)
ilinc(other._annotinc[0]);
if (other._have_xlstart)
xlstart(other._annotstart[1]);
if (other._have_xlinc)
xlinc(other._annotinc[1]);
if (other._have_zstart)
zstart(other._zstart);
if (other._have_zinc)
zinc(other._zinc);
if (other._have_corners)
corners(other._corners);
return *this;
}
ZgyWriterArgs&
ZgyWriterArgs::compressor(const std::string& name, const std::vector<std::string>& args)
{
......
......@@ -424,6 +424,7 @@ private:
friend class Impl::ZgyWriter; // Processing the high level parts.
friend class ::Test::ZgyWriterMock;
friend class Impl::EnumMapper; // Mapping the rest to the internal struct.
friend class IZgyWriter; // For IZgyWriter::reopen()
std::string _filename;
const IOContext *_iocontext; // TODO-Worry: Sure instance is short lived?
compressor_t _compressor;
......@@ -438,6 +439,23 @@ private:
float _zstart, _zinc;
std::array<float,2> _annotstart, _annotinc;
corners_t _corners;
// Keeps track of what information has been changed from the default.
// Currently metafrom() will set all to true. That might change.
// The information is only used by merge(). Transient information
// such as iocontext is ignored.
bool _have_size;
bool _have_bricksize;
bool _have_datatype;
bool _have_datarange;
bool _have_zunit;
bool _have_hunit;
bool _have_ilstart;
bool _have_ilinc;
bool _have_xlstart;
bool _have_xlinc;
bool _have_zstart;
bool _have_zinc;
bool _have_corners;
public:
ZgyWriterArgs()
......@@ -460,32 +478,45 @@ public:
, _annotstart{0,0}
, _annotinc{0,0}
, _corners{0,0,0,0,0,0,0,0}
, _have_size(false)
, _have_bricksize(false)
, _have_datatype(false)
, _have_datarange(false)
, _have_zunit(false)
, _have_hunit(false)
, _have_ilstart(false)
, _have_ilinc(false)
, _have_xlstart(false)
, _have_xlinc(false)
, _have_zstart(false)
, _have_zinc(false)
, _have_corners(false)
{
}
/**
* \brief Output in human readable form for debugging.
*/
void dump(std::ostream& out)
void dump(std::ostream& out) const
{
out << "ZgyWriterArgs\n"
<< " filename: \"" << _filename << "\"\n"
<< " iocontext: " << (_iocontext ? "*" : "(null)") << "\n"
<< " compressor: " << (_compressor ? "*" : "(null)") << "\n"
<< " lodcompress: " << (_lodcompressor ? "*" : "(null)") << "\n"
<< " size: (" << _size[0] << "," << _size[1] << "," << _size[2] << ")\n"
<< " bricksize: (" << _bricksize[0] << "," << _bricksize[1] << "," << _bricksize[2] << ")\n"
<< " datatype: " << int(_datatype) << "\n"
<< " datarange: " << _datarange[0] << " to " << _datarange[1] << "\n"
<< " zunit: " << int(_zunitdim) << " \"" << _zunitname << "\" " << _zunitfactor << "\n"
<< " hunit: " << int(_hunitdim) << " \"" << _hunitname << "\" " << _hunitfactor << "\n"
<< " ilstart/inc: " << _annotstart[0] << " / " << _annotinc[0] << "\n"
<< " xlstart/inc: " << _annotstart[1] << " / " << _annotinc[1] << "\n"
<< " zstart/inc: " << _zstart << " / " << _zinc << "\n"
<< " corner0: " << _corners[0][0] << ", " << _corners[0][1] << "\n"
<< " corner1: " << _corners[1][0] << ", " << _corners[1][1] << "\n"
<< " corner2: " << _corners[2][0] << ", " << _corners[2][1] << "\n"
<< " corner3: " << _corners[3][0] << ", " << _corners[3][1] << "\n";
<< " " << (_have_size?"*":"") << "size: (" << _size[0] << "," << _size[1] << "," << _size[2] << ")\n"
<< " " << (_have_bricksize?"*":"") << "bricksize: (" << _bricksize[0] << "," << _bricksize[1] << "," << _bricksize[2] << ")\n"
<< " " << (_have_datatype?"*":"") << "datatype: " << int(_datatype) << "\n"
<< " " << (_have_datarange?"*":"") << "datarange: " << _datarange[0] << " to " << _datarange[1] << "\n"
<< " " << (_have_zunit?"*":"") << "zunit: " << int(_zunitdim) << " \"" << _zunitname << "\" " << _zunitfactor << "\n"
<< " " << (_have_hunit?"*":"") << "hunit: " << int(_hunitdim) << " \"" << _hunitname << "\" " << _hunitfactor << "\n"
<< " " << (_have_ilstart||_have_ilinc?"*":"") << "ilstart/inc: " << _annotstart[0] << " / " << _annotinc[0] << "\n"
<< " " << (_have_xlstart||_have_xlinc?"*":"") << "xlstart/inc: " << _annotstart[1] << " / " << _annotinc[1] << "\n"
<< " " << (_have_zstart||_have_zinc?"*":"") << "zstart/inc: " << _zstart << " / " << _zinc << "\n"
<< " " << (_have_corners?"*":"") << "corner0: " << _corners[0][0] << ", " << _corners[0][1] << "\n"
<< " " << (_have_corners?"*":"") << "corner1: " << _corners[1][0] << ", " << _corners[1][1] << "\n"
<< " " << (_have_corners?"*":"") << "corner2: " << _corners[2][0] << ", " << _corners[2][1] << "\n"
<< " " << (_have_corners?"*":"") << "corner3: " << _corners[3][0] << ", " << _corners[3][1] << "\n";
}
/**
......@@ -537,16 +568,16 @@ public:
* \param nj number of crosslines.
* \param nk number of samples per trace (fastest).
*/
ZgyWriterArgs& size(std::int64_t ni, std::int64_t nj, std::int64_t nk) { _size[0]=ni; _size[1]=nj; _size[2]=nk; return *this; }
ZgyWriterArgs& size(std::int64_t ni, std::int64_t nj, std::int64_t nk) { _size[0]=ni; _size[1]=nj; _size[2]=nk; _have_size = true; return *this; }
/**
* \brief Set size of one brick.
* \details Almost always (64,64,64). Change at your own peril.
*/
ZgyWriterArgs& bricksize(std::int64_t ni, std::int64_t nj, std::int64_t nk) { _bricksize[0]=ni; _bricksize[1]=nj; _bricksize[2]=nk; return *this; }
ZgyWriterArgs& bricksize(std::int64_t ni, std::int64_t nj, std::int64_t nk) { _bricksize[0]=ni; _bricksize[1]=nj; _bricksize[2]=nk; _have_bricksize = true; return *this; }
/**
* \brief Set type of samples in each brick.
*/
ZgyWriterArgs& datatype(SampleDataType value) { _datatype = value; return *this; }
ZgyWriterArgs& datatype(SampleDataType value) { _datatype = value; _have_datatype = true; return *this; }
/**
* \brief Set scaling factors.
* \details For integral storage this specifies the two floating
......@@ -557,7 +588,7 @@ public:
* ZGY files require that min<max. This is not enforced here,
* but is checked when the file is actually created.
*/
ZgyWriterArgs& datarange(float lo, float hi) { _datarange[0] = lo; _datarange[1] = hi; return *this; }
ZgyWriterArgs& datarange(float lo, float hi) { _datarange[0] = lo; _datarange[1] = hi; _have_datarange = true; return *this; }
/**
* \brief Set vertical unit.
* \param dimension time or depth (a.k.a. length).
......@@ -568,6 +599,7 @@ ZgyWriterArgs& datarange(float lo, float hi) { _datarange[0] = lo; _datarange[1]
_zunitdim = dimension;
_zunitname = name;
_zunitfactor = factor;
_have_zunit = true;
return *this;
}
/**
......@@ -580,32 +612,33 @@ ZgyWriterArgs& datarange(float lo, float hi) { _datarange[0] = lo; _datarange[1]
_hunitdim = dimension;
_hunitname = name;
_hunitfactor = factor;
_have_hunit = true;
return *this;
}
/// \brief Set first (ordinal 0) inline number.
/// \details For maximum portability the inline and crossline start
/// and increment should be integral numbers. Some applications
/// might choose to convert them to int.
ZgyWriterArgs& ilstart(float value) { _annotstart[0] = value; return *this; }
ZgyWriterArgs& ilstart(float value) { _annotstart[0] = value; _have_ilstart = true; return *this; }
/// \brief Set inline number increment between two adjacent ordinal values.
/// \copydetails ilstart
ZgyWriterArgs& ilinc(float value) { _annotinc[0] = value; return *this; }
ZgyWriterArgs& ilinc(float value) { _annotinc[0] = value; _have_ilinc = true; return *this; }
/// \brief Set first (ordinal 0) crossline number.
/// \copydetails ilstart
ZgyWriterArgs& xlstart(float value) { _annotstart[1] = value; return *this; }
ZgyWriterArgs& xlstart(float value) { _annotstart[1] = value; _have_xlstart = true; return *this; }
/// \brief Set crossline number increment between two adjacent ordinal values.
/// \copydetails ilstart
ZgyWriterArgs& xlinc(float value) { _annotinc[1] = value; return *this; }
ZgyWriterArgs& xlinc(float value) { _annotinc[1] = value; _have_xlinc = true; return *this; }
/// \brief Set first time/depth.
/// \details Vertical annotation is generally safe to have non-integral.
ZgyWriterArgs& zstart(float value) { _zstart = value; return *this; }
ZgyWriterArgs& zstart(float value) { _zstart = value; _have_zstart = true; return *this; }
/// \brief Set increment (distance between samples) in vertical direction.
/// \copydetails zstart
ZgyWriterArgs& zinc(float value) { _zinc = value; return *this; }
ZgyWriterArgs& zinc(float value) { _zinc = value; _have_zinc = true; return *this; }
/// \brief Set survey corner points in world coordinates.
/// \details The corners are ordered origin, last inline (i.e. i=last, j=0),
/// last crossline, diagonal.
ZgyWriterArgs& corners(const corners_t& value) { _corners = value; return *this; }
ZgyWriterArgs& corners(const corners_t& value) { _corners = value; _have_corners = true; return *this; }
/**
* \brief Copy metadata from existing file.
*
......@@ -616,6 +649,15 @@ ZgyWriterArgs& datarange(float lo, float hi) { _datarange[0] = lo; _datarange[1]
*/
ZgyWriterArgs& metafrom(const std::shared_ptr<OpenZGY::IZgyReader>&);
/**
* \brief Copy metadata from another ZgyWriterArgs.
*
* Copy only those settings that have been explicitly changed in the
* supplied "other" ZgyWriterArgs into *this. If other.metafrom()
* has been called it is unspecified what gets copied.
*/
ZgyWriterArgs& merge(const ZgyWriterArgs&);
// TODO-Low: Add accessors as well. But these are only for internal
// use so the ugliness of accessing data members isn't that bad.
};
......@@ -790,6 +832,8 @@ public:
/// \endcond
/// \brief Create a ZGY file and open it for writing.
static std::shared_ptr<IZgyWriter> open(const ZgyWriterArgs& args);
/// \brief Open an existing ZGY file for writing.
static std::shared_ptr<IZgyWriter> reopen(const ZgyWriterArgs& args);
};
/**
......
......@@ -65,6 +65,30 @@ namespace {
}
}
namespace {
#if 0
}
#endif
#ifdef HAVE_SD
/**
* Convenience to hard code credentials for testing. Returns an IOContext.
* Picking up sdurl/sdapikey from the environment is redundant since
* the library already does this as a fallback.
*/
SeismicStoreIOContext getContext()
{
using InternalZGY::Environment;
return SeismicStoreIOContext()
.sdurl(Environment::getStringEnv("OPENZGY_SDURL"))
.sdapikey(Environment::getStringEnv("OPENZGY_SDAPIKEY"))
.sdtoken(Environment::getStringEnv("OPENZGY_TOKEN") != "" ?
Environment::getStringEnv("OPENZGY_TOKEN") :
"FILE:carbon.slbapp.com", "");
}
#endif
}
namespace Test_API {
#if 0
}
......@@ -594,18 +618,21 @@ void test_ZgyWriterArgs()
TEST_CHECK(ss.str().find("\"ms\"") != std::string::npos);
}
void test_write()
static void do_write_once(const std::string& filename, const IOContext *context = nullptr)
{
LocalFileAutoDelete lad("testfile.zgy");
ZgyWriterArgs args = ZgyWriterArgs()
.filename(lad.name())
.iocontext(context)
.filename(filename)
.size(33, 28, 92)
.datatype(SampleDataType::int16)
.datarange(-32768,+32767)
.ilstart(1).ilinc(2)
.xlstart(500).xlinc(5)
.zstart(100).zinc(4)
.zunit(UnitDimension::time, "ms", 1000);
.hunit(UnitDimension::length, "m", 1)
.zunit(UnitDimension::time, "ms", 1000)
.corners(ZgyWriterArgs::corners_t{5,7,5,107,205,7,205,107});
std::shared_ptr<OpenZGY::IZgyWriter> writer = OpenZGY::IZgyWriter::open(args);
std::vector<float> data(2*3*4, -1000);
const OpenZGY::IZgyWriter::size3i_t origin{0,0,0};
......@@ -616,14 +643,19 @@ void test_write()
writer->write(origin, count, data.data());
writer->finalize(std::vector<OpenZGY::DecimationType>{}, nullptr);
writer->close();
}
std::shared_ptr<OpenZGY::IZgyReader> reader = OpenZGY::IZgyReader::open(lad.name());
static void do_check_written(const std::string& filename, const IOContext* context = nullptr)
{
std::shared_ptr<OpenZGY::IZgyReader> reader = OpenZGY::IZgyReader::open(filename, context);
if (verbose()) {
std::cout << "\n";
dump_api(reader, std::cout);
//reader->dump(std::cout);
}
std::unique_ptr<float[]> checkdata(new float[64*64*64]);
const OpenZGY::IZgyWriter::size3i_t origin{0,0,0};
const OpenZGY::IZgyWriter::size3i_t bsize{64,64,64};
reader->read(origin, bsize, checkdata.get(), 0);
TEST_CHECK(checkdata[0] == -1000);
TEST_CHECK(checkdata[63] == 42);
......@@ -633,9 +665,115 @@ void test_write()
TEST_CHECK(h.samplecount == 33*28*92);
TEST_CHECK(h.bins[124] == 2*3*4);
TEST_CHECK(h.bins[128] == 33*28*92 - 2*3*4);
const ZgyWriterArgs::corners_t corners = reader->corners();
const double eps = 1.0e-10;
TEST_CHECK(fabs(corners[0][0] - 5) <= eps);
TEST_CHECK(fabs(corners[0][1] - 7) <= eps);
TEST_CHECK(fabs(corners[1][0] - 5) <= eps);
TEST_CHECK(fabs(corners[1][1] - 107) <= eps);
TEST_CHECK(fabs(corners[2][0] - 205) <= eps);
TEST_CHECK(fabs(corners[2][1] - 7) <= eps);
TEST_CHECK(fabs(corners[3][0] - 205) <= eps);
TEST_CHECK(fabs(corners[3][1] - 107) <= eps);
TEST_CHECK(reader->size() == (OpenZGY::IZgyWriter::size3i_t{33, 28, 92}));
TEST_CHECK(reader->datatype() == SampleDataType::int16);
TEST_CHECK(reader->hunitname() == "m");
TEST_CHECK(reader->zunitname() == "ms");
TEST_CHECK(reader->annotstart()[0] == 1);
TEST_CHECK(reader->annotstart()[1] == 500);
TEST_CHECK(reader->zstart() == 100);
reader->close();
}
/**
* As do_write_once() but the file gets opened, closed, and opened again.
*/
static void do_write_twice(const std::string& filename, const IOContext* context = nullptr)
{
ZgyWriterArgs firstargs = ZgyWriterArgs()
.iocontext(context)
.filename(filename)
.size(33, 28, 92)
.bricksize(64, 64, 64)
.datatype(SampleDataType::int16)
.datarange(-32768,+32767);
ZgyWriterArgs secondargs = ZgyWriterArgs()
.iocontext(context)
.filename(filename)
.ilstart(1).ilinc(2)
.xlstart(500).xlinc(5)
.zstart(100).zinc(4)
.hunit(UnitDimension::length, "m", 1)
.zunit(UnitDimension::time, "ms", 1000)
.corners(ZgyWriterArgs::corners_t{5,7,5,107,205,7,205,107});
// Create with basic information only.
std::shared_ptr<OpenZGY::IZgyWriter> writer =
OpenZGY::IZgyWriter::open(firstargs);
writer->finalize(std::vector<OpenZGY::DecimationType>{}, nullptr);
writer->close();
writer.reset();
// Try to re-open it. Should work since there are no data blocks.
writer = IZgyWriter::reopen(secondargs);
// Re-open again, re-specifying the basic info (no error unless it changes)
// and not re-specifying the mutable part (should then be retained).
writer->close();
writer.reset();
writer = IZgyWriter::reopen(firstargs);
// Continue as in test_write(). Probably should have refactored to share code.
std::vector<float> data(2*3*4, -1000);
const OpenZGY::IZgyWriter::size3i_t origin{0,0,0};
const OpenZGY::IZgyWriter::size3i_t bsize{64,64,64};
const OpenZGY::IZgyWriter::size3i_t count{2,3,4};
float fortytwo{42};
writer->writeconst(origin, bsize, &fortytwo);
writer->write(origin, count, data.data());
writer->finalize(std::vector<OpenZGY::DecimationType>{}, nullptr);
writer->close();
}
void test_write()
{
LocalFileAutoDelete lad("testfile.zgy");
do_write_once(lad.name());
do_check_written(lad.name());
}
void test_update()
{
LocalFileAutoDelete lad("updatelocal.zgy");
do_write_twice(lad.name());
do_check_written(lad.name());
}
#ifdef HAVE_SD
void
test_write_cloud()
{
SeismicStoreIOContext context = getContext();
// TODO-Low, this CloudFileAutoDelete doesn't actually clean up.
// Hopefully the test or the cleanup task will handle it.
Test_Utils::CloudFileAutoDelete cad("writecloud.zgy");
do_write_once(cad.name(), &context);
do_check_written(cad.name(), &context);
}
void
test_update_cloud()
{
SeismicStoreIOContext context = getContext();
// TODO-Low, this CloudFileAutoDelete doesn't actually clean up.
// Hopefully the test or the cleanup task will handle it.
Test_Utils::CloudFileAutoDelete cad("updatecloud.zgy");
do_write_twice(cad.name(), &context);
do_check_written(cad.name(), &context);
}
#endif
void test_historange()
{
LocalFileAutoDelete lad("testhisto.zgy");
......@@ -1279,6 +1417,11 @@ public:
register_test("api.readbadvt", test_readbadvt);
register_test("api.readbadpos", test_readbadpos);
register_test("api.write", test_write);
register_test("api.update", test_update);
#ifdef HAVE_SD
register_test("api.write_cloud", test_write_cloud);
register_test("api.update_cloud", test_update_cloud);
#endif
register_test("api.historange", test_historange);
register_test("api.lod_lowpass", test_lod_lowpass);
register_test("api.lod_weighted", test_lod_weighted);
......
......@@ -1083,6 +1083,7 @@ ZgyWriter_create(ZgyClass* self, PyObject* args, PyObject* keywds)
const_cast<char*>("lodcompressor"), // O
const_cast<char*>("zfp_compressor"), // f
const_cast<char*>("zfp_lodcompressor"), // f
const_cast<char*>("update"), // p
NULL
};
......@@ -1104,8 +1105,9 @@ ZgyWriter_create(ZgyClass* self, PyObject* args, PyObject* keywds)
PyObject* compressor_obj{nullptr};
PyObject* lodcompressor_obj{nullptr};
float zfp_snr = 0, zfp_lodsnr = 0;
int update{0};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s(iii)|$(iii)O(ff)OOssddff(ff)(ff)((dd)(dd)(dd)(dd))OOOff", kwlist,
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s(iii)|$(iii)O(ff)OOssddff(ff)(ff)((dd)(dd)(dd)(dd))OOOffp", kwlist,
&filename,
&size[0], &size[1], &size[2], // (iii)
&bricksize[0], &bricksize[1], &bricksize[2], // (iii)
......@@ -1128,6 +1130,7 @@ ZgyWriter_create(ZgyClass* self, PyObject* args, PyObject* keywds)