/** * Creates an new MetaData or replaces its data if it already exists. * * This method constructs a MetaData without value, with the label @a * label, an empty description and is unlocked. * * @ingroup BTKCommon */ MetaData::Pointer MetaDataCreateChild(MetaData::Pointer parent, const std::string& label) { MetaData::Pointer entry = MetaData::New(label); if (parent != MetaData::Null) { MetaData::Iterator it = parent->FindChild(label); if (it != parent->End()) { (*it)->SetDescription(""); (*it)->SetInfo(MetaDataInfo::Pointer()); (*it)->SetUnlockState(true); return (*it); } else { parent->AppendChild(entry); return entry; } } else { btkWarningMacro("No parent."); return entry; } };
/** * Sets the threshold value. * * The threshold must be activated (see GroundReactionWrenchFilter::SetThresholdState) to be used during the computation of the PWA. */ void GroundReactionWrenchFilter::SetThresholdValue(double v) { if (fabs(this->m_ThresholdValue - v) <= std::numeric_limits<double>::epsilon()) return; if (v < 0.0) btkWarningMacro("Negative threshold has no effect on the algorithm because it compares the threshold value with the absolute value of Fz."); this->m_ThresholdValue = v; this->Modified(); };
/** * Finish the computation of the ground reaction wrench for the Kislter force platform. */ void GroundReactionWrenchFilter::FinishKistler(Wrench::Pointer wrh, ForcePlatform::Pointer fp, int index) { ForcePlatform::Origin origin; origin << 0, 0, fp->GetOrigin().z(); if (origin.z() > 0) { btkWarningMacro("Vertical offset between the origin of the force platform #" + ToString(index) + " and the center of the working surface seems to be misconfigured (positive value). The opposite of this offset is used."); origin.z() *= -1; } this->FinishGRWComputation(wrh, origin); };
/** * Finish the computation of the ground reaction wrench for the AMTI force platforms. */ void GroundReactionWrenchFilter::FinishAMTI(Wrench::Pointer wrh, ForcePlatform::Pointer fp, int index) { ForcePlatform::Origin origin = fp->GetOrigin(); if (origin.z() > 0) { btkWarningMacro("Origin for the force platform #" + ToString(index) + " seems to be located from the center of the working surface instead of the inverse. Data are inverted to locate the center of the working surface from the platform's origin."); origin *= -1; } this->FinishGRWComputation(wrh, origin); };
/** * Try to set gain from the given value @a g. * The input must represent one of the value associated with the enum Analog::Gain. * These value can be interpreted as the half ot voltage range expressed in mV. */ void Analog::SetGainFromValue(int g) { switch(g) { case Analog::PlusMinus10: case Analog::PlusMinus5: case Analog::PlusMinus2Dot5: case Analog::PlusMinus1Dot65: case Analog::PlusMinus1Dot25: case Analog::PlusMinus1: case Analog::PlusMinus0Dot5: case Analog::PlusMinus0Dot25: case Analog::PlusMinus0Dot1: case Analog::PlusMinus0Dot05: this->SetGain(static_cast<Analog::Gain>(g)); break; default: btkWarningMacro("Unknown gain. Replaced by a gain of +/- 10 volts."); this->SetGain(Analog::PlusMinus10); break; } };
/** * Read the file designated by @a filename and fill @a output. */ void ANCFileIO::Read(const std::string& filename, Acquisition::Pointer output) { output->Reset(); // Open the stream std::ifstream ifs; ifs.exceptions(std::ios_base::eofbit | std::ios_base::failbit | std::ios_base::badbit); try { std::string line; ifs.open(filename.c_str()); // Check the first header keyword: "File_Type:" std::getline(ifs, line); if (line.substr(0,41).compare("File_Type: Analog R/C ASCII Generation#: ") != 0) throw(ANCFileIOException("Invalid ANC file.")); // Check the file generation. this->m_Generation = FromString<int>(line.substr(41,1)); if ((this->m_Generation != 1) && (this->m_Generation != 2)) throw(ANCFileIOException("Unknown ANC file generation: " + line.substr(42,43) + ".")); // Extract header data // Board_Type & Polarity std::getline(ifs, line); std::string boardType = this->ExtractKeywordValue(line, "Board_Type: "); std::string polarity = this->ExtractKeywordValue(line, "Polarity: "); // Trial_Name, Trial#, Duration(Sec.), #Channels std::getline(ifs, line); double duration = FromString<double>(this->ExtractKeywordValue(line, "Duration(Sec.): ")); size_t numberOfChannels = FromString<size_t>(this->ExtractKeywordValue(line, "#Channels: ")); // BitDepth & PreciseRate std::getline(ifs, line); int bitDepth = FromString<int>(this->ExtractKeywordValue(line, "BitDepth: ")); double preciseRate = FromString<double>(this->ExtractKeywordValue(line, "PreciseRate: ")); // Four next lines are empty std::getline(ifs, line); std::getline(ifs, line); std::getline(ifs, line); std::getline(ifs, line); // DEVELOPER CHECK // Check polarity's value. Only Bipolar is supported for the moment. if (polarity.compare("Bipolar") != 0) throw(ANCFileIOException("Unsupported ANC file. Only Bipolar board type is supported for the moment. Please, send an email to the developers to explain the problem.")); // Extract data if (numberOfChannels != 0) { // Analog channels' label std::list<std::string> labels, rates, ranges; std::getline(ifs, line); this->ExtractDataInfo(line, "Name", labels); size_t numberOfLabels = labels.size(); if (numberOfChannels != numberOfLabels) { btkWarningMacro(filename, "Mismatch between the number of analog channels and the number of labels extracted. Final number of analog channels corresponds to the number of labels extracted."); numberOfChannels = numberOfLabels; } // Analog channels' rate std::getline(ifs, line); this->ExtractDataInfo(line, "Rate", rates); // Analog channels' range std::getline(ifs, line); this->ExtractDataInfo(line, "Range", ranges); double nf = duration * preciseRate; // Must be separate in two step due to some rounding errors size_t numberOfFrames = static_cast<size_t>(nf) + 1; // Data conversion std::vector<std::string> channelLabel(labels.size()); std::vector<uint16_t> channelRate(rates.size()); std::vector<uint16_t> channelRange(ranges.size()); int inc = 0; for (std::list<std::string>::const_iterator it = rates.begin() ; it != rates.end() ; ++it) FromString(*it, channelRate[inc++]); inc = 0; for (std::list<std::string>::const_iterator it = ranges.begin() ; it != ranges.end() ; ++it) FromString(*it, channelRange[inc++]); inc = 0; for (std::list<std::string>::const_iterator it = labels.begin() ; it != labels.end() ; ++it) channelLabel[inc++] = *it; ANxFileIOCheckHeader_p(preciseRate, numberOfChannels, channelRate, channelRange); ANxFileIOStoreHeader_p(output, filename, preciseRate, numberOfFrames, numberOfChannels, channelLabel, channelRate, channelRange, boardType, bitDepth, this->m_Generation); // Extract values std::string buf; double val = 0.0; for(size_t i = 0 ; i < numberOfFrames ; ++i) { ifs >> buf; // Time's value for (AnalogCollection::Iterator it = output->BeginAnalog() ; it != output->EndAnalog() ; ++it) { ifs >> val; (*it)->GetValues().coeffRef(static_cast<int>(i)) = val * (*it)->GetScale(); } } } // Add a metadata to notify that the first frame was not set. MetaData::Pointer btkPointConfig = MetaDataCreateChild(output->GetMetaData(), "BTK_POINT_CONFIG"); MetaDataCreateChild(btkPointConfig, "NO_FIRST_FRAME", static_cast<int8_t>(1)); }
/** * Read the file designated by @a filename and fill @a output. */ void BSFFileIO::Read(const std::string& filename, Acquisition::Pointer output) { output->Reset(); IEEELittleEndianBinaryFileStream bifs; bifs.SetExceptions(BinaryFileStream::EndFileBit | BinaryFileStream::FailBit | BinaryFileStream::BadBit); try { bifs.Open(filename, BinaryFileStream::In); // Main header (SMnHeaderTag) if (bifs.ReadI32() != 100) throw BSFFileIOException("Invalid BSF file."); int32_t headerSize = bifs.ReadI32(); int32_t numberOfActivePlatforms = bifs.ReadI32(); int32_t numberOfActiveInstruments = bifs.ReadI32(); std::string subjectName = btkTrimString(bifs.ReadString(100)); std::string testDate = btkTrimString(bifs.ReadString(12)); std::string subjectDateOfBirth = btkTrimString(bifs.ReadString(12)); double weight = bifs.ReadDouble(); double height = bifs.ReadDouble(); std::string sex = btkTrimString(bifs.ReadString(1)); bifs.SeekRead(3, BinaryFileStream::Current); // Because of "pragma pack(4)": minimum of 4 bytes in the alignment of a member. int32_t numberOfTrials = bifs.ReadI32(); double totaleTimeTrial = bifs.ReadDouble(); // seconds int32_t zeroMethod = bifs.ReadI32(); int32_t weightMethod = bifs.ReadI32(); int32_t delayAfterKeystroke = bifs.ReadI32(); int32_t triggerMethod = bifs.ReadI32(); int32_t triggerPlatform = bifs.ReadI32(); int32_t preTriggerValue = bifs.ReadI32(); int32_t postTriggerValue = bifs.ReadI32(); double triggerValue = bifs.ReadDouble(); bifs.SeekRead(4, BinaryFileStream::Current); // FIXME: There is 4 extra bytes in the file used to test this reader! What are they? int32_t rate = bifs.ReadI32(); std::string protocol = btkTrimString(bifs.ReadString(150)); std::string testType = btkTrimString(bifs.ReadString(200)); std::string commentFile = btkTrimString(bifs.ReadString(150)); std::string trialDescriptionFile = btkTrimString(bifs.ReadString(150)); std::string examinerName = btkTrimString(bifs.ReadString(100)); bifs.SeekRead(2, BinaryFileStream::Current); // WARNING: Two (2) extra bytes in the file tested. Could be for alignment? int32_t units = bifs.ReadI32(); // 0: english, 1: metric if (rate == 0) throw BSFFileIOException("Invalid frame rate."); // Instrument headers (SInstrHeaderTag) std::vector<InstrumentHeader> instrumentHeaders(numberOfActivePlatforms + numberOfActiveInstruments); for (int i = 0 ; i < numberOfActivePlatforms ; ++i) this->extractInstrumentHeader(&bifs, &(instrumentHeaders[i])); for (int i = 0 ; i < numberOfActiveInstruments ; ++i) this->extractInstrumentHeader(&bifs, &(instrumentHeaders[i+numberOfActivePlatforms])); // Internal check to verify the compatibility between BTK acquisition's format and data for (size_t i = 0 ; i < instrumentHeaders.size() ; ++i) { if ((instrumentHeaders[i].rate != 0) && (instrumentHeaders[i].rate != rate)) throw BSFFileIOException("Unsupported file: An instrument has a different rate. Contact the developers to improve this reader."); } for (int i = 0 ; i < numberOfActivePlatforms ; ++i) { if (instrumentHeaders[i].numberOfChannels != 6) throw BSFFileIOException("Unsupported file: A force platform has more than 6 channels. Contact the developers to improve this reader."); } // Init int totalNumberOfChannels = 0; for (size_t i = 0 ; i < instrumentHeaders.size() ; ++i) totalNumberOfChannels += instrumentHeaders[i].numberOfChannels; double* scale = new double[totalNumberOfChannels]; // array to transform ADC values to real values size_t inc = 0; for (size_t i = 0 ; i < instrumentHeaders.size() ; ++i) { for (int j = 0 ; j < instrumentHeaders[i].numberOfChannels ; ++j) scale[j+inc] = 1000000.0 / (instrumentHeaders[i].sensitivity[j] * instrumentHeaders[i].amplifierGain[j] * instrumentHeaders[i].excitationVoltage[j] * instrumentHeaders[i].acquisitionCardRange[j]); inc += instrumentHeaders[i].numberOfChannels; } output->Init(0, static_cast<int>(totaleTimeTrial * static_cast<double>(rate)), totalNumberOfChannels); output->SetPointFrequency(static_cast<double>(rate)); if (units == 0) // english { output->SetPointUnit(Point::Marker, "in"); output->SetPointUnit(Point::Force, "lb"); output->SetPointUnit(Point::Moment, "in-lb"); } else { // The FP length/width seems to be set everytime in inch even if the header is set // to metric units (so only used for measured value?). for (size_t i = 0 ; i < instrumentHeaders.size() ; ++i) { // Convert from inch to meter instrumentHeaders[i].length *= 0.0254f; instrumentHeaders[i].width *= 0.0254f; instrumentHeaders[i].offset[0] *= 0.0254f; instrumentHeaders[i].offset[1] *= 0.0254f; instrumentHeaders[i].offset[2] *= 0.0254f; } // Same for the content of the measure. The BSF file format seems to save the data in pound and pound-inch for (int i = 0 ; i < numberOfActivePlatforms ; ++i) { scale[i*6] *= 4.4482216152605; scale[i*6+1] *= 4.4482216152605; scale[i*6+2] *= 4.4482216152605; scale[i*6+3] *= 0.1129848290276167; scale[i*6+4] *= 0.1129848290276167; scale[i*6+5] *= 0.1129848290276167; } output->SetPointUnit(Point::Marker, "m"); output->SetPointUnit(Point::Force, "N"); output->SetPointUnit(Point::Moment, "Nm"); } Acquisition::AnalogIterator it = output->BeginAnalog(); std::vector<float> corners(12*numberOfActivePlatforms, 0.0f); std::vector<float> origin(3*numberOfActivePlatforms, 0.0f); std::vector<int16_t> channel(6*numberOfActivePlatforms); std::string suffix = ((numberOfActivePlatforms == 1) ? "" : "1"); float globalOrigin[3] = {0.0f, 0.0f, 0.0f}; int numToAdaptChannelIndex = 0; for (int i = 0 ; i < numberOfActivePlatforms ; ++i) { (*it)->SetLabel("Fx" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Force)); (*it)->SetScale(scale[i*6]); ++it; (*it)->SetLabel("Fy" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Force)); (*it)->SetScale(scale[i*6+1]); ++it; (*it)->SetLabel("Fz" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Force)); (*it)->SetScale(scale[i*6+2]); ++it; (*it)->SetLabel("Mx" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Moment)); (*it)->SetScale(scale[i*6+3]); ++it; (*it)->SetLabel("My" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Moment)); (*it)->SetScale(scale[i*6+4]); ++it; (*it)->SetLabel("Mz" + suffix); (*it)->SetUnit(output->GetPointUnit(Point::Moment)); (*it)->SetScale(scale[i*6+5]); ++it; if (i > 0) { if ((instrumentHeaders[i].interDistance[0] == 0.0f) && (instrumentHeaders[i].interDistance[1] == 0.0f) && (instrumentHeaders[i].interDistance[2] == 0.0f)) { btkWarningMacro(filename, "The distance with the previous force platform is set to 0. The platform is automatically shifted in the front of the previous. You might have to modify the origin of the force platform #" + ToString(i) + "in the metadata FORCE_PLATFORM:ORIGIN to locate it correctly in the global frame."); instrumentHeaders[i].interDistance[1] = static_cast<float>(instrumentHeaders[i].length + instrumentHeaders[i-1].length) / 2.0f; } } globalOrigin[0] += instrumentHeaders[i].interDistance[0]; globalOrigin[1] += instrumentHeaders[i].interDistance[1]; globalOrigin[2] += instrumentHeaders[i].interDistance[2]; this->extractConfiguration(&(instrumentHeaders[i]), globalOrigin, &(channel[i*6]), &(corners[i*12]), &(origin[i*3])); for (int j = 0 ; j < 6 ; ++j) channel[i*6+j] += numToAdaptChannelIndex; numToAdaptChannelIndex += instrumentHeaders[i].numberOfChannels; suffix = ToString(i+2); } // Create the metadata FORCE_PLATFORM btk::MetaData::Pointer forcePlatform = btk::MetaData::New("FORCE_PLATFORM"); output->GetMetaData()->AppendChild(forcePlatform); // - FORCE_PLATFORM:USED forcePlatform->AppendChild(btk::MetaData::New("USED", static_cast<int16_t>(numberOfActivePlatforms))); // - FORCE_PLATFORM:TYPE forcePlatform->AppendChild(btk::MetaData::New("TYPE", std::vector<int16_t>(numberOfActivePlatforms,2))); // - FORCE_PLATFORM:ZERO std::vector<int16_t> zero(2,0); zero[0] = 1; forcePlatform->AppendChild(btk::MetaData::New("ZERO", zero)); // - FORCE_PLATFORM:CORNERS std::vector<uint8_t> dims(3); dims[0] = 3; dims[1] = 4; dims[2] = numberOfActivePlatforms; forcePlatform->AppendChild(btk::MetaData::New("CORNERS", dims, corners)); // - FORCE_PLATFORM:ORIGIN dims.resize(2); dims[0] = 3; dims[1] = numberOfActivePlatforms; forcePlatform->AppendChild(btk::MetaData::New("ORIGIN", dims, origin)); // - FORCE_PLATFORM:CHANNEL dims.resize(2); dims[0] = 6; dims[1] = numberOfActivePlatforms; forcePlatform->AppendChild(btk::MetaData::New("CHANNEL", dims, channel)); // - FORCE_PLATFORM:CAL_MATRIX dims.resize(3); dims[0] = 6; dims[1] = 6; dims[2] = 0; forcePlatform->AppendChild(btk::MetaData::New("CAL_MATRIX", dims, std::vector<float>())); // Add a metadata to notify that the first frame was not set. MetaData::Pointer btkPointConfig = MetaDataCreateChild(output->GetMetaData(), "BTK_POINT_CONFIG"); MetaDataCreateChild(btkPointConfig, "NO_FIRST_FRAME", static_cast<int8_t>(1)); // Data // Note: We want the reaction of the measure, so all the data are multiplied by -1. for (int i = 0 ; i < output->GetAnalogFrameNumber() ; ++i) { int inc = 0; for (Acquisition::AnalogIterator it = output->BeginAnalog() ; it != output->EndAnalog() ; ++it) (*it)->GetValues().coeffRef(i) = -1.0f * static_cast<double>(bifs.ReadI16()) * scale[inc++]; } // Cleaning delete[] scale; } catch (BinaryFileStreamFailure& ) { std::string excmsg; if (bifs.EndFile()) excmsg = "Unexpected end of file."; else if (!bifs.IsOpen()) excmsg = "Invalid file path."; else if(bifs.Bad()) excmsg = "Loss of integrity of the file stream."; else if(bifs.Fail()) excmsg = "Internal logic operation error on the stream associated with the file."; else excmsg = "Unknown error associated with the file stream."; if (bifs.IsOpen()) bifs.Close(); throw(BSFFileIOException(excmsg)); } catch (BSFFileIOException& ) { if (bifs.IsOpen()) bifs.Close(); throw; } catch (std::exception& e) { if (bifs.IsOpen()) bifs.Close(); throw(BSFFileIOException("Unexpected exception occurred: " + std::string(e.what()))); } catch(...) { if (bifs.IsOpen()) bifs.Close(); throw(BSFFileIOException("Unknown exception")); } };