ihedvall / mdflib

Implementation of the ASAM MDF data file.
https://ihedvall.github.io/mdflib/
MIT License
73 stars 30 forks source link

Saving time stamps with integer data types #101

Closed csavorgn closed 2 months ago

csavorgn commented 2 months ago

Hi, I'm using mdflib to save some generic data in mdf4 format using a MdfWriterType::Mdf4Basic writer. For my application I'd need to save the sample time with the greatest precision possible. So far I created a master channel using double precision floating point data:

mdf::IChannel *tmp = channel_group->CreateChannel();
tmp->Name("time");
tmp->Description("Time channel");
tmp->Type(mdf::ChannelType::Master);
tmp->Sync(ChannelSyncType::Time);
tmp->DataType(mdf::ChannelDataType::FloatLe);
tmp->DataBytes(8);
tmp->Unit("s");

However I'd prefer to save directly the time using a 64 bit unsigned integer. I tried to do it but then the file reader I'm using complains that the time stamp is not monotonous increasing. Is there a way to achieve what I want? Thanks Carlo

ihedvall commented 2 months ago

I am currently offline so I can't test if my solution works. The master scaled value shall be relative time in seconds. Adding a conversion block to the channel that scale the nanoseconds to seconds should do it but I don't now if it works. The writer recalculate the absolute time to relative before saving. You can try. I am back tomorrow.

csavorgn commented 2 months ago

I tried the following code

mdf::IChannel *tmp = mdf_file->channel_group->CreateChannel();
tmp->Name("time");
tmp->Description("Time channel");
tmp->Type(mdf::ChannelType::Master);
tmp->Sync(ChannelSyncType::Time);
tmp->DataType(mdf::ChannelDataType::UnsignedIntegerLe);
tmp->DataBytes(8);
tmp->Unit("ns");
auto* conv = tmp->CreateChannelConversion();
conv->Name("conv_ns_to_s");
conv->Description("conversion from ns to s");
conv->Unit("s");
conv->Type(ConversionType::Linear);
conv->Parameter(0, 0.0);
conv->Parameter(1,1e-9);

But I keep getting the same error. I tried also to change the last coefficient to 1e9 but with no difference. I'm not sure I understood completely how to use the conversions... Carlo

ihedvall commented 2 months ago

I have checked the code and it doesn't handle unsigned integers channel types. The solution is to add the other channel types. Your solution is correct except that the channel unit is set to ns instead of s. You think logically correct about the units but the ASAM guys think different, the channel unit is the scaled unit which is weird thinking.

I will add the missing switch/case statements in the code above so your code should work. This fix will not solve all type of master configurations so maybe an "Do not recalculate the master time" flag should be added. The user then must set the master value itself.

void IChannel::SetTimestamp(double timestamp,
                            std::vector<uint8_t> &record_buffer) const {
  // If conversion is in use, reverse convert to channel value
  const auto* conversion = ChannelConversion();
  if (conversion != nullptr &&
      conversion->Type() == ConversionType::Linear &&
      conversion->Parameter(1) != 0.0) {
    timestamp -= conversion->Parameter(0);
    timestamp /= conversion->Parameter(1);
  } else if (conversion != nullptr) {
    return;
  }

  const size_t bytes = BitCount() / 8;

  switch (DataType()) {
    case ChannelDataType::FloatLe:
      if (bytes == 4) {
        const LittleBuffer data(static_cast<float>(timestamp));
        memcpy(record_buffer.data() + ByteOffset(), data.data(), bytes);
      } else if (bytes == 8) {
        const LittleBuffer data(static_cast<double>(timestamp));
        memcpy(record_buffer.data() + ByteOffset(), data.data(), bytes);
      }
      break;

    case ChannelDataType::FloatBe:
      if (bytes == 4) {
        const BigBuffer data(static_cast<float>(timestamp));
        memcpy(record_buffer.data() + ByteOffset(), data.data(), bytes);
      } else if (bytes == 8) {
        const BigBuffer data(static_cast<double>(timestamp));
        memcpy(record_buffer.data() + ByteOffset(), data.data(), bytes);
      }
      break;

    default:
      break;
  }
}
ihedvall commented 2 months ago

Just a correction. The channel type should be a signed integer as the relative timestamp can be negative. Pre-trig time > 0.

ihedvall commented 2 months ago

@csavorgn I have added so it is possible to save the master time channel as a signed integer. Your example is OK but you shouldn't use unsigned integer as the relative time can be negative. The linear conversion factors should be 0/1E-9;

I have also added a CalculateMasterTime property to the channel object in case you want some advanced conversion. If set to false you need to set the master channel value. The property is true by default.

Please check these changes against your viewer to verify that the changes are OK.

csavorgn commented 2 months ago

@ihedvall Thank you! I just tried the new code and it works using SignedIntegerLe. However, to get the unit right I had to set the units as I did originally (ns for the channel and s for the channel conversion). I'm using the GUI of the python package asammdf to visualize the data.

ihedvall commented 2 months ago

@csavorgn Yes, I think you are correct regarding the units. The channel value unit is nanoseconds while the scaled value unit is seconds. The MDF standard is incomplete in this matter.