Binär-Daten auslesen



  • Welchen Wert hat denn pos?

    Der richtige Datentyp sollte uint16_t sein.
    Nackt, nicht in einer struct verpackt.

    Und bei

    if(buffer.buf & (1<<12))
            {
                buffer.buf -= 4096;
    ....
            }
    

    solltest du dir mal ansehen, wie man ein Bit löscht. Nebenbei sind 1<<12 die 4096.



  • Dirk, Danke für die Anregungen.

    pos hat in dem o. g. Fall 244 (int) und wird sich stets etwa in dieser Region befinden. Macht es in dem Fall einen Unterschied, ob ich int oder uint16_t verwende?

    Oki, das Bit könnte ich auch so löschen:

    buffer.buf &= ~(1<<12);
    

    Effektiv dürfte aber bei beidem das gleiche rauskommen, oder? Zumindest ist die output.txt indentisch. Aber schöner ist deine Variante. Danke!



  • MediStef schrieb:

    Macht es in dem Fall einen Unterschied, ob ich int oder uint16_t verwende?

    Ja.
    Vergleich mal sizeof von den Beiden.
    Und du hast ja auch nicht ohne Grund unsigned short gewählt.
    Bei uint16_t kannst du dir aber sicher sein, das es 16-Bit sind.



  • aso Dirk, du meinst der Datentyp vom buffer... hab ich falsch verstanden und ist geändert. Dann hab ich das struct weg.

    uint16_t buffer;
    .
    .
    .
    readFile.read((char *)&buffer, 2);
    .
    .
    .
    if(buffer & (1<<12))
    {
    	//cout << "Sekundärer Datensatz\r\n";
    	buffer &= ~(1<<12);
    }
    .
    .
    .
    map.push_back (buffer);
    

    Aber am Ergebnis ändert es leider nix.



  • Hallo Stefan,

    Du beginnst mit ' getline ', um den Header zu überlesen:

    MediStef schrieb:

    // open binary data file
    	ifstream readFile;
    	readFile.open("data.bin", ios::in | ios::binary);
    
    	//skip header
    	string line;
    	getline(readFile, line); 
    		int pos = line.find(0x03);
    	readFile.seekg(pos+1);
    

    getline liest bis zum ersten LF (Linefeed; Code = 10; bzw. 0xA0). In der Datei auf die Du velinkst hast, ist der Header aber nicht mit einem LF abgeschlossen, d.h. getline liest viel weiter, bis in die Pixel-Daten hinein.

    Das Format ist ein Fortran-Format, d.h. die Anzahl der Zeichen pro Datum ist (meistens) konstant. Die geeignete Methode den Header zu überlesen, wäre also ignore - genauer

    readFile.ignore( numeric_limits< streamsize >::max(), char(3) );
    

    Gruß
    Werner



  • Kann er nicht das 0x03 bei getline als delimiter angeben? 😕



  • Hey Werner,

    jopp, hast Recht. Danke! Formal wäre es schöner, die Zeichen zu zählen, aber der Header muss soll mit variabler Länge ausgelesen werden, weil der sich ändern kann.

    Ich hab das mal abgeändert um nen paar Zeichen zu sparen:

    string header;
    char delim = 0x03;
    getline(readFile, header, delim);
    

    Der Header schließt mit ETX (0x03) ab. So steht der Zeiger nach dem Steuerzeichen.

    Das Ergebnis ist aber das leider gleiche.



  • PS.: die Datei enthält tatsächlich 900x903 Datensätze a 2 Byte

    .. ja - readfile.ignore( .. ,char(3)) ist die erste Wahl (s.o.), wenn der Header nicht benötigt wird. Ich würde aber immer auch die ersten beiden Zeichen lesen und auf 'SF' prüfen.



  • Danke Werner für die Rückmeldung. Wie hast du die Datei ausgelesen? Hast du mein Prog übernommen oder es mit nem anderen Reader gemacht?

    Ich bleib bei meiner Variante, da ich den Header eventuell noch benötige.

    Wenn das tatsächlich keine 900x900 sind, sondern mehr, muss ich wohl mal ein Wörtchen mit dem DWD sprechen. 🕶 Oder hab ich was Wichtiges in der PDF überlesen?



  • MediStef schrieb:

    Danke Werner für die Rückmeldung. Wie hast du die Datei ausgelesen? Hast du mein Prog übernommen oder es mit nem anderen Reader gemacht?

    Hallo Stefan,

    ich habe einfach ignore-bis-char(3) gemacht und dann die verbleibenden Bytes gezählt. Dazu waren nur eine Handvoll Programmzeilen notwendig.

    MediStef schrieb:

    Wenn das tatsächlich keine 900x900 sind, sondern mehr, muss ich wohl mal ein Wörtchen mit dem DWD sprechen. 🕶 Oder hab ich was Wichtiges in der PDF überlesen

    Vielleicht - es gibt noch zwei Informationen über die Anzahl die folgenden Datensätze.

    Zum einen steht hinter der Kennung 'GP' die Anzahl der Pixel - hier '900x900' und hinter der Kennung 'BY' steht noch mal die Größe des gesamten Datensatzes inklusive des Headers - hier '1620245' bei einer Header-Länge von 245 sind das genau 1620000 Byte macht 1620000/2=810000=900x900 Datensätze.
    Die beiden Angaben sind redundant, da die Datensatzlänge von 2Byte für das 'SF'-Format festgelegt ist; im Grunde ist das schlechter Stil - na ja - wahrscheinlich ein 'gewachsenes' Format.

    Ich würde Dir zunächst mal empfehlen eine struct für so einen Datensatz zu bauen:

    #include <cstdint> // std::uint16_t
    #include <cassert>
    #include <istream>
    
    namespace RADOLAN
    {
        struct Datensatz
        {
            Datensatz()
                : data_(0x29C4) // invalid
            {}
            bool is_cluttermarkierung() const
            {
                return (data_ & 0x8000) != 0;
            }
            bool valid() const
            {
                return (data_ & 0x2000) == 0;
            }
            int get() const
            {
                assert( valid() );
                int ret = data_ & 0x0fff;
                if( data_ & 0x4000 ) // Vorzeichen-Bit
                    ret = -ret;
                return ret;
            }
            friend std::istream& operator>>( std::istream& in, Datensatz& d );
    
        private:
            std::uint16_t data_; // ist sicher 2 Byte groß!
        };
    }
    

    Der Streaming-Operator in Zeile 28 liest so einen Datensatz binär ein - Implementierung:

    namespace RADOLAN
    {
        // --   Einlesen im Binärformat aus 2 byte (little endian)
        std::istream& operator>>( std::istream& in, Datensatz& d )
        {
            std::istream::sentry ok( in, true ); // true := noskip von 'White Space Charaters'
            if( ok )
            {
                std::ios_base::iostate state = std::ios_base::goodbit;
                try
                {
                    char buf[2];
                    if( in.rdbuf()->sgetn( buf, 2 ) == 2 )
                    {   // little endian -> LOW Byte; HIGH Byte
                        d.data_ = static_cast< std::uint16_t >( static_cast< unsigned char >( buf[0] ) )
                            | (static_cast< std::uint16_t >( static_cast< unsigned char >( buf[1] ) ) << 8);
                    }
                    else
                        state |= std::ios_base::eofbit | std::ios_base::failbit;
                }
                catch( ... )
                {
                    state |= std::ios_base::badbit;
                    if( in.exceptions() & std::ios_base::badbit )
                        throw;
                }
                in.setstate( state );
            }
            return in;
        }
    }
    

    Ich habe diesen Extractor wie auch die folgenden gleich auf 'streambuf-Ebene' realisiert - näheres dazu findest Du hier: http://www.c-plusplus.net/forum/p2256837#2256837

    Mit Hilfe des obigen Codes kannst Du so eine Datei sehr leicht einlesen:

    #include <iostream>
    #include <fstream>
    #include <limits>
    #include <vector>
    
    int main()
    {
        using namespace std;
        using namespace RADOLAN;
        const char ETX(3);
    
        ifstream readfile( "data.bin", ios_base::binary );
        if( !readfile.is_open() )
        {
            cerr << "Fehler beim Oeffnen" << endl;
            return -2;
        }
        readfile.ignore( numeric_limits< streamsize >::max(), ETX ); // Header überlesen
    
        vector< Datensatz > pixel;
        int count = 0;
        for( Datensatz d; count < 900*900 && readfile >> d; ++count )
            pixel.push_back( d );
        if( readfile )
            cout << "ohne Fehler gelesen" << endl;
    }
    

    MediStef schrieb:

    Ich bleib bei meiner Variante, da ich den Header eventuell noch benötige.

    .. dann solltest Du diesen auch lesen und nicht nur die Zeichen in einen String kopieren 😉

    Der Header besteht aus mehreren Einträgen, die immer mit einer Kennung beginnen. Für die Kennung habe ich auch ein Helferlein gebastelt, so kann man z.B. prüfen, ob das überhaupt ein 'SF'-Format ist

    // -- einfügen vor Zeile 18
        Kennung kennung;
        if( (readfile >> kennung).fail() || kennung != "SF" )
        {
            cerr << "kein RADOLAN-SF-Dateiformat" << endl;
            return -3;
        }
    

    Die Implementierung dazu:

    #include <string>
    #include <istream>
    
    namespace RADOLAN
    {
        struct Kennung
        {
            Kennung() : str_()  {}
            bool operator==( const std::string& b ) const
            {
                return str_ == b;
            }
            bool operator!=( const std::string& b ) const
            {
                return str_ != b;
            }
            friend std::istream& operator>>( std::istream& in, Kennung& tok );
        private:
            std::string str_;
        };
    
        std::istream& operator>>( std::istream& in, Kennung& tok )
        {
            std::istream::sentry ok( in, true );
            if( ok )
            {
                std::ios_base::iostate state = std::ios_base::goodbit;
                try
                {
                    typedef std::istream::traits_type Traits;
                    const char ETX(3);
                    std::string str;
                    for( int i=0; i<2; ++i )
                    {
                        Traits::int_type m = in.rdbuf()->sbumpc();
                        if( Traits::eq_int_type( m, Traits::eof() ) )
                        {
                            state |= std::ios_base::eofbit | std::ios_base::failbit;
                            break;
                        }
                        const char c = Traits::to_char_type(m);
                        str.append( 1, c );
                        if( c == ETX )
                            break;
                    }
                    if( state == std::ios_base::goodbit )
                        swap( str, tok.str_ );
                }
                catch( ... )
                {
                    state |= std::ios_base::badbit;
                    if( in.exceptions() & std::ios_base::badbit )
                        throw;
                }
                in.setstate( state );
            }
            return in;
        }
    }
    

    das allein macht noch keinen Sinn, ohne auch die Parameter hinter den Kennungen zu lesen. Der std::istream ist per se nicht in der Lage Zahlen mit einer festen Anzahl von Ziffern einzulesen und dann das Lesen zu beenden. Im Allgemeinen muss ein Datum (Zahl oder String) immer mit einen White-Space-Character beendet werden, damit es gelesen werden kann. Das ist bei diesem FORTRAN-Format nicht der Fall.

    Deshalb ist ein weiteres Helferlein fällig, welches Integer mit fester Stellen liest. Damit kann dann z.B. hinter "SF" das Datum gelesen werden:

    int tag, monat, jahr;
        if( (readfile >> number<2>( tag )).ignore( 2*2+5 ) >> number<2>( monat ) >> number<2>( jahr ) )
        {   // Format: 3I2 I5 2I2
            cout << "Datum: " << tag << "." << monat << ".20" << jahr << endl;
        }
    

    Die Implementierung von number<> :

    namespace RADOLAN
    {
        // --   der 'number_reader' liest eine Folge von genau 'N' Ziffern in eine Variable
        //      vom Typ 'T' ein. (Dezimalsystem)
        template< int N, typename T >
        struct number_reader
        {
            number_reader( T& number )
                : number_( number ) {}
    
            friend std::istream& operator>>( std::istream& in, number_reader nr )
            {
                std::istream::sentry ok( in, true ); // true := noskip
                if( ok )
                {
                    T number = 0;
                    std::ios_base::iostate state = std::ios_base::goodbit;
                    try
                    {
                        typedef std::istream::traits_type Traits;
                        bool digit_read = false;
                        const std::ctype< char >& ct = std::use_facet< std::ctype< char > >( in.getloc() );
                        for( int i=0; i<N; ++i )
                        {
                            const Traits::int_type m = in.rdbuf()->sbumpc();
                            if( Traits::eq_int_type( m, Traits::eof() ) )
                            {
                                state |= std::ios_base::eofbit | std::ios_base::failbit;
                                break;
                            }
                            const char c = Traits::to_char_type( m );
                            if( ct.is( std::ctype_base::digit, c ) || (!digit_read && ct.is( std::ctype_base::space, c )) )
                            {
                                T digit = 0;
                                if( ct.is( std::ctype_base::digit, c ) )
                                {
                                    digit_read = true;
                                    digit = T(c - '0');
                                }
                                if( number > (std::numeric_limits< T >::max() - digit)/10  )
                                {   // overflow
                                    state |= std::ios_base::failbit;
                                    break;
                                }
                                (number *= 10) += digit;
                            }
                            else
                            {
                                state |= std::ios_base::failbit;
                                break;
                            }
                        }
                        if( (state & std::ios_base::failbit) == 0 )
                            nr.number_ = number;
                    }
                    catch( ... )
                    {
                        state |= std::ios_base::badbit;
                        if( in.exceptions() & std::ios_base::badbit )
                            throw;
                    }
                    in.setstate( state );
                }
                return in;
            }
        private:
            T& number_;
        };
    
        template< int N, typename T >
        number_reader< N, T > number( T& n )
        {
            return number_reader< N, T >( n );
        }
    }
    

    Damit ist dann auch der nächste Datensatz 'BY' kein Problem mehr:

    size_t size;
        readfile >> kennung;
        if( kennung != "BY" )
            readfile.setstate( ios_base::failbit );
        readfile >> number<7>( size ); // Format I7
    

    .. die weiteren überlasse ich Dir.

    Gruß
    Werner


Anmelden zum Antworten