|
WWW:
Use this form to send
us your feedback. |
|
Orders:
Use this form to place
your order(s). |
|
Corporate Office:
Voice:(949) 858-4216
Fax: (949) 858-5022 |
|
Sales and Service:
Voice:(858) 874-2547
Fax: (858) 874-2549 |
|
E-Mail:
ariel1@ix.netcom.com
gideon@arielnet.com |
|
Corporate Office
6 Alicante Street
Trabuco Canyon, CA 92679
U.S.A. |
|
Sales and Service:
4885 Ronson Court, Suite A
San Diego, CA 92111
U.S.A. |
|
The
Webmaster
6 Alicante
Trabuco Canyon, CA 92679
U.S.A. |
|
Files Formats for the APAS System
We have a document on our WEB site which contains the specifications for
the
different file formats You can find it at:
/APAShelp/DOS/adw-56w.html
The CF/3D files are binary files. When one uses the EXPORT capabilities of
the APAS software, data is extracted from these binary files and presented
in rows/cols of ascii tables.
I hope this helps,
Jeremy Wise, Dir R&D
Analog .ana file
Yes indeed the total number of samples divided by the
number of channels will give the number of datapoints per channel.
The ENVREC contains all the information about
the raw channels. The conversion of A/D units to user units is performed in two steps.
First there is a conversion of A/D units to volts. This involves properties of the A/D
board itself such as the #bits, voltage range, gain, etc and is performed as follows:
float SOFTCHAN_AD::Raw2Volts(int iVal)
/*AD->User Units*/
{
return (iVal - chanInfo.iADZeroV) *
chanInfo.fAD2Volts/chanInfo.fGain;
}
The 2nd step is to convert volts to user units
which is hardware independent. The conversion of raw to user is calculated as follows:
float SOFTCHAN_AD::Raw2User(int iVal)
/*AD->User Units*/
{
return (Raw2Volts(iVal)-chanInfo.fOff)*chanInfo.fConv;
}
I'm sure some of the structure of the ANA files
is somewhat confusing. This is largely because the structure of the file has evolved over
the last 20 some years. New capability was added while always maintaining backwards
compatibility. Clearly if one were setting out today to design the structure of the file
it would be done differently.
The other pieces of information in the ENVREC
that are important for the force plates are:
1) at WORD offset=267 is the 1st Plate channel.
It need not be that the 1st actual channel collected belongs to a force plate. It is
assumed that the force plate channels are consecutive from the 1st plate channel. The
order of the channels depends on whether the plate is a 6 channel plate such as AMTI in
which case the order is assumed to be:
If it is an 8 channel plate such as Kistler then
the order is:
Fx12|Fx34|Fy14|Fy23|Fz1|Fz2|Fz3|Fz4
Additional plates directly follow with the same
assumed channel order. There can be only 1 type of plate 6 or 8 channel at any one time.
2) at WORD offset=283 Plate dimension units
at WORD offset=296 Plate Type
0=Kistler 1=AMTI 2=Bertec
at WORD offset=310 #Plates on
system
at WORD offset=311 for 2
plates 6 element dimension description
1="X" Distance centerline to transducers
2="Y" Distance centerline to transducers
3=Depth beneath plate surface for the transducers [always positive]
at WORD offset=335
orientation of 2nd plate relative to 1st.
The only item of interest in the EXTENVREC is
at WORD offset=103 Force
units for the plate. 1=Nt 2=Kg 3=Lb
Here is code for calculating Fxyz, Mxyz for a
Kistler plate. GetVal(channel, sample#) returns the channel value for specified sample# in
user units.
/***************************************************************/
float KISTLER::RtnFx(LONG lIndx)
// Override Base class member
{
float xRet=0.;
for (int i=0;i<2;i++)
xRet+=m_sc.GetVal(iChan1st+i,lIndx);
return(xRet);
}
/***************************************************************/
float KISTLER::RtnFy(LONG lIndx)
// Override Base class member
{
float xRet=0.;
for (int i=0;i<2;i++)
xRet+=m_sc.GetVal(iChan1st+i+2,lIndx);
return(xRet);
}
/***************************************************************/
float KISTLER::RtnFz(LONG lIndx)
// Override Base class member
{
float xRet=0.;
for (int i=0;i<4;i++)
xRet+=m_sc.GetVal(iChan1st+i+4,lIndx);
return(xRet);
}
/***************************************************************/
float KISTLER::RtnMx(LONG lIndx)
// Override Base class member
{
return(plateDims.fB*
(m_sc.GetVal(iChan1st+4,lIndx)
+ m_sc.GetVal(iChan1st+5,lIndx)
- m_sc.GetVal(iChan1st+6,lIndx)
- m_sc.GetVal(iChan1st+7,lIndx)));
}
/***************************************************************/
float KISTLER::RtnMy(LONG lIndx)
// Override Base class member
{
return(plateDims.fA*
(-m_sc.GetVal(iChan1st+4,lIndx)
+ m_sc.GetVal(iChan1st+5,lIndx)
+ m_sc.GetVal(iChan1st+6,lIndx)
- m_sc.GetVal(iChan1st+7,lIndx)));
}
/***************************************************************/
float KISTLER::RtnMz(LONG lIndx)
// Override Base class member
{
return(plateDims.fB*
(-m_sc.GetVal(iChan1st,lIndx)
+ m_sc.GetVal(iChan1st+1,lIndx)) + plateDims.fA*
(m_sc.GetVal(iChan1st+2,lIndx)
- m_sc.GetVal(iChan1st+3,lIndx)));
}
From Fxyz & Mxyz the center of pressure and
the free moment can be calculated as follows:
/***************************************************************/
float FORCE_PLATE::RtnAx(LONG lIndx)
// Returns COP-X value for plate
// lIndx- sample frame [offset one]
{
float xRet,fz=RtnFz(lIndx);
if (fz>fThresh)
{
xRet=(RtnFx(lIndx)*plateDims.fAz-RtnMy(lIndx))/fz;
if (xRet<(-plateDims.fLx/2.) || xRet>plateDims.fLx/2.) file://Off Plate?
xRet=0.;
}
else
xRet=0.;
return(xRet);
}
/***************************************************************/
float FORCE_PLATE::RtnAy(LONG lIndx)
// Returns COP-Y value for plate
// lIndx- sample frame [offset one]
{
float xRet=0.,fz=RtnFz(lIndx);
if (fz>fThresh)
{
xRet=(RtnFy(lIndx)*plateDims.fAz+RtnMx(lIndx))/fz;
if (xRet<(-plateDims.fLy/2.) || xRet>plateDims.fLy/2.) // Off
Plate?
xRet=0.;
}
return(xRet);
}
/***************************************************************/
float FORCE_PLATE::RtnMzfree(LONG lIndx)
// Returns Moment-Zfree value for plate
// lIndx- sample frame [offset one]
{
float xRet,fz=RtnFz(lIndx);
if (fz>fThresh)
xRet=RtnMz(lIndx) - RtnFy(lIndx)*RtnAx(lIndx)
+ RtnFx(lIndx)*RtnAy(lIndx);
else
xRet=0.;
return(xRet);
}
I hope this helps. I am leaving Fri 2/18 for a week so fire
away before I leave with more questions.
I would like to verify what I need from the
ANA-files for reading force and torque data from either 1 or 2 force platforms. I
am attempting to write an ANA-file loader for the APAS Gait, but this is not trivial. I
already ironed out some problems with the basic types (int, short, float) due to
Intel's non-standard byte-ordering.
My only sources of information are some poorly
documented Matlab files (attached) and the documentation from the web. From this I
understood there may be multiple trials in one file. I figured out
how to get to the trial names, and get data from a user-selected
trial.
Is the method in the MatLab-files for calculating
forces and torques from the trial data block correct? For either 1 or 2
platforms? Apparently the total number of samples divided by the number of channels
will give the number of datapoints per channel, correct? Then in FORCE.M the
final mappings are done to map channel data to actual forces and torques, using 2
conversion factors and an offset from the environment block. Is this calculation correct?
Are these channels always in the same order? Or should I assume the same channel
names? And are they always present, even with 1 platform?
Any hints, tips, advice are most welcome.
Thanks so much for your help. Hope to hear from you.
|
|