Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

887

888

889

890

891

892

893

894

895

896

897

898

899

900

901

902

903

904

905

906

907

908

909

910

911

912

913

914

915

916

917

918

919

920

921

922

923

924

925

926

927

928

929

930

931

932

933

934

935

936

937

938

939

940

941

942

943

944

945

946

947

948

949

950

951

952

953

954

955

956

957

958

959

960

961

962

963

964

965

966

967

968

969

970

971

972

973

974

975

976

977

978

979

980

981

982

983

984

985

986

987

988

989

990

991

992

993

994

995

996

997

998

999

1000

1001

1002

1003

1004

1005

1006

1007

1008

1009

1010

1011

1012

1013

1014

1015

1016

1017

1018

1019

1020

1021

1022

1023

1024

1025

1026

1027

1028

1029

1030

1031

1032

1033

1034

1035

1036

1037

1038

1039

1040

1041

1042

1043

1044

# -*- coding: utf-8 -*- 

#------------------------------------------------------------------- 

#  Filename: seg.py 

#  Purpose: Routines for reading and writing SEG Y files. 

#   Author: Lion Krischer 

#    Email: krischer@geophysik.uni-muenchen.de 

# 

# Copyright (C) 2010 Lion Krischer 

#--------------------------------------------------------------------- 

""" 

Routines to read and write SEG Y rev 1 encoded seismic data files. 

""" 

 

from obspy.segy.header import ENDIAN, DATA_SAMPLE_FORMAT_UNPACK_FUNCTIONS, \ 

    BINARY_FILE_HEADER_FORMAT, DATA_SAMPLE_FORMAT_PACK_FUNCTIONS, \ 

    TRACE_HEADER_FORMAT, DATA_SAMPLE_FORMAT_SAMPLE_SIZE, TRACE_HEADER_KEYS 

from obspy.segy.util import unpack_header_value 

from struct import pack, unpack 

from unpack import OnTheFlyDataUnpacker 

import StringIO 

import numpy as np 

import os 

 

 

class SEGYError(Exception): 

    """ 

    Base SEGY exception class. 

    """ 

    pass 

 

 

class SEGYTraceHeaderTooSmallError(SEGYError): 

    """ 

    Raised if the trace header is not the required 240 byte long. 

    """ 

    pass 

 

 

class SEGYTraceReadingError(SEGYError): 

    """ 

    Raised if there is not enough data left in the file to unpack the data 

    according to the values read from the header. 

    """ 

    pass 

 

 

class SEGYTraceOnTheFlyDataUnpackingError(SEGYError): 

    """ 

    Raised if attempting to unpack trace data but no ``unpack_data()`` function 

    exists. 

    """ 

    pass 

 

 

class SEGYWritingError(SEGYError): 

    """ 

    Raised if the trace header is not the required 240 byte long. 

    """ 

    pass 

 

 

class SEGYFile(object): 

    """ 

    Class that internally handles SEG Y files. 

    """ 

    def __init__(self, file=None, endian=None, textual_header_encoding=None, 

                 unpack_headers=False, headonly=False): 

        """ 

        Class that internally handles SEG Y files. 

 

        :param file: A file like object with the file pointer set at the 

            beginning of the SEG Y file. If file is None, an empty SEGYFile 

            object will be initialized. 

        :param endian: The endianness of the file. If None, autodetection will 

            be used. 

        :param textual_header_encoding: The encoding of the textual header. 

            Either 'EBCDIC', 'ASCII' or None. If it is None, autodetection will 

            be attempted. If it is None and file is also None, it will default 

            to 'ASCII'. 

        :param unpack_headers: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. Defaults to False. 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be read and unpacked. Has a huge impact on memory 

            usage. Data can be read and unpacked on-the-fly after reading the 

            file. Defaults to False. 

        """ 

        if file is None: 

            self._createEmptySEGYFileObject() 

            # Set the endianness to big. 

            if endian is None: 

                self.endian = '>' 

            else: 

                self.endian = ENDIAN[endian] 

            # And the textual header encoding to ASCII. 

            if textual_header_encoding is None: 

                self.textual_header_encoding = 'ASCII' 

            self.textual_header = '' 

            return 

        self.file = file 

        # If endian is None autodetect is. 

        if not endian: 

            self._autodetectEndianness() 

        else: 

            self.endian = ENDIAN[endian] 

        # If the textual header encoding is None, autodetection will be used. 

        self.textual_header_encoding = textual_header_encoding 

        # Read the headers. 

        self._readHeaders() 

        # Read the actual traces. 

        self._readTraces(unpack_headers=unpack_headers, headonly=headonly) 

 

    def __str__(self): 

        """ 

        Prints some information about the SEG Y file. 

        """ 

        return '%i traces in the SEG Y structure.' % len(self.traces) 

 

    def _autodetectEndianness(self): 

        """ 

        Tries to automatically determine the endianness of the file at hand. 

        """ 

        pos = self.file.tell() 

        # Jump to the data sample format code. 

        self.file.seek(3224, 1) 

        format = unpack('>h', self.file.read(2))[0] 

        # Check if valid. 

        if format in DATA_SAMPLE_FORMAT_UNPACK_FUNCTIONS.keys(): 

            self.endian = '>' 

        # Else test little endian. 

        else: 

            self.file.seek(-2, 1) 

            format = unpack('<h', self.file.read(2))[0] 

            if format in DATA_SAMPLE_FORMAT_UNPACK_FUNCTIONS.keys(): 

                self.endian = '<' 

            else: 

                msg = 'Unable to determine the endianness of the file. ' + \ 

                      'Please specify it.' 

                raise SEGYError(msg) 

        # Jump to previous position. 

        self.file.seek(pos, 0) 

 

    def _createEmptySEGYFileObject(self): 

        """ 

        Creates an empty SEGYFile object. 

        """ 

        self.textual_file_header = '' 

        self.binary_file_header = None 

        self.traces = [] 

 

    def _readTextualHeader(self): 

        """ 

        Reads the textual header. 

        """ 

        # The first 3200 byte are the textual header. 

        textual_header = self.file.read(3200) 

        # The data can either be saved as plain ASCII or EBCDIC. The first 

        # character always is mostly 'C' and therefore used to check the 

        # encoding. Sometimes is it not C but also cannot be decoded from 

        # EBCDIC so it is treated as ASCII and all empty symbols are removed. 

        if not self.textual_header_encoding: 

            if textual_header[0] != 'C': 

                try: 

                    textual_header = \ 

                        textual_header.decode('EBCDIC-CP-BE').encode('ascii') 

                    # If this worked, the encoding is EBCDIC. 

                    self.textual_header_encoding = 'EBCDIC' 

                except UnicodeEncodeError: 

                    textual_header = textual_header 

                    # Otherwise it is ASCII. 

                    self.textual_header_encoding = 'ASCII' 

            else: 

                # Otherwise the encoding will also be ASCII. 

                self.textual_header_encoding = 'ASCII' 

        elif self.textual_header_encoding.upper() == 'EBCDIC': 

            textual_header = \ 

                textual_header.decode('EBCDIC-CP-BE').encode('ascii') 

        elif self.textual_header_encoding.upper() != 'ASCII': 

            msg = """ 

            The textual_header_encoding has to be either ASCII, EBCDIC or None 

            for autodetection. ASCII, EBCDIC or None for autodetection. 

            """.strip() 

            raise SEGYError(msg) 

        # Finally set it. 

        self.textual_file_header = textual_header 

 

    def _readHeaders(self): 

        """ 

        Reads the textual and binary file headers starting at the current file 

        pointer position. 

        """ 

        # Read the textual header. 

        self._readTextualHeader() 

        # The next 400 bytes are from the Binary File Header. 

        binary_file_header = self.file.read(400) 

        bfh = SEGYBinaryFileHeader(binary_file_header, self.endian) 

        self.binary_file_header = bfh 

        self.data_encoding = self.binary_file_header.data_sample_format_code 

        # If bytes 3506-3506 are not zero, an extended textual header follows 

        # which is not supported so far. 

        if bfh.number_of_3200_byte_ext_file_header_records_following != 0: 

            msg = 'Extended textual headers are supported yet. ' + \ 

                   'Please contact the developers.' 

            raise NotImplementedError(msg) 

 

    def write(self, file, data_encoding=None, endian=None): 

        """ 

        Write a SEG Y file to file which is either a file like object with a 

        write method or a filename string. 

 

        If data_encoding or endian is set, these values will be enforced. 

        """ 

        if not hasattr(file, 'write'): 

            with open(file, 'wb') as file: 

                self._write(file, data_encoding=data_encoding, endian=endian) 

            return 

        self._write(file, data_encoding=data_encoding, endian=endian) 

 

    def _write(self, file, data_encoding=None, endian=None): 

        """ 

        Writes SEG Y to a file like object. 

 

        If data_encoding or endian is set, these values will be enforced. 

        """ 

        # Write the textual header. 

        self._writeTextualHeader(file) 

 

        # Write certain fields in the binary header if they are not set. Most 

        # fields will be written using the data from the first trace. It is 

        # usually better to set the header manually! 

        if self.binary_file_header.number_of_data_traces_per_ensemble <= 0: 

            self.binary_file_header.number_of_data_traces_per_ensemble = \ 

                len(self.traces) 

        if self.binary_file_header.sample_interval_in_microseconds <= 0: 

            self.binary_file_header.sample_interval_in_microseconds = \ 

                self.traces[0].header.sample_interval_in_ms_for_this_trace 

        if self.binary_file_header.number_of_samples_per_data_trace <= 0: 

            self.binary_file_header.number_of_samples_per_data_trace = \ 

                    len(self.traces[0].data) 

 

        # Always set the SEGY Revision number to 1.0 (hex-coded). 

        self.binary_file_header.seg_y_format_revision_number = 16 

        # Set the fixed length flag to zero if all traces have NOT the same 

        # length. Leave unchanged otherwise. 

        if len(set([len(tr.data) for tr in self.traces])) != 1: 

            self.binary_file_header.fixed_length_trace_flag = 0 

        # Extended textual headers are not supported by ObsPy so far. 

        self.binary_file_header.\ 

        number_of_3200_byte_ext_file_header_records_following = 0 

        # Enforce the encoding 

        if data_encoding: 

            self.binary_file_header.data_sample_format_code = data_encoding 

 

        # Write the binary header. 

        self.binary_file_header.write(file, endian=endian) 

        # Write all traces. 

        for trace in self.traces: 

            trace.write(file, data_encoding=data_encoding, endian=endian) 

 

    def _writeTextualHeader(self, file): 

        """ 

        Write the textual header in various encodings. The encoding will depend 

        on self.textual_header_encoding. If self.textual_file_header is too 

        small it will be padded with zeros. If it is too long or an invalid 

        encoding is specified an exception will be raised. 

        """ 

        length = len(self.textual_file_header) 

        # Append spaces to the end if its too short. 

        if length < 3200: 

            textual_header = self.textual_file_header + ' ' * (3200 - length) 

        elif length == 3200: 

            textual_header = self.textual_file_header 

        # The length must not exceed 3200 byte. 

        else: 

            msg = 'self.textual_file_header is not allowed to be longer ' + \ 

                  'than 3200 bytes' 

            raise SEGYWritingError(msg) 

        if self.textual_header_encoding.upper() == 'ASCII': 

            pass 

        elif self.textual_header_encoding.upper() == 'EBCDIC': 

            textual_header = \ 

                textual_header.decode('ascii').encode('EBCDIC-CP-BE') 

        # Should not happen. 

        else: 

            msg = 'self.textual_header_encoding has to be either ASCII or ' + \ 

                  'EBCDIC.' 

            raise SEGYWritingError(msg) 

        file.write(textual_header) 

 

    def _readTraces(self, unpack_headers=False, headonly=False): 

        """ 

        Reads the actual traces starting at the current file pointer position 

        to the end of the file. 

 

        :param unpack_headers: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. Defaults to False. 

 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be read and unpacked. Has a huge impact on memory 

            usage. Data can be read and unpacked on-the-fly after reading the 

            file. Defaults to False. 

        """ 

        self.traces = [] 

        # Determine the filesize once. 

        if isinstance(self.file, StringIO.StringIO): 

            filesize = self.file.len 

        else: 

            filesize = os.fstat(self.file.fileno())[6] 

        # Big loop to read all data traces. 

        while True: 

            # Read and as soon as the trace header is too small abort. 

            try: 

                trace = SEGYTrace(self.file, self.data_encoding, self.endian, 

                                  unpack_headers=unpack_headers, 

                                  filesize=filesize, headonly=headonly) 

                self.traces.append(trace) 

            except SEGYTraceHeaderTooSmallError: 

                break 

 

 

class SEGYBinaryFileHeader(object): 

    """ 

    Parses the binary file header at the given starting position. 

    """ 

    def __init__(self, header=None, endian='>'): 

        """ 

        """ 

        self.endian = endian 

        if header is None: 

            self._createEmptyBinaryFileHeader() 

            return 

        self._readBinaryFileHeader(header) 

 

    def _readBinaryFileHeader(self, header): 

        """ 

        Reads the binary file header and stores every value in a class 

        attribute. 

        """ 

        pos = 0 

        for item in BINARY_FILE_HEADER_FORMAT: 

            length, name, _ = item 

            string = header[pos: pos + length] 

            pos += length 

            # Unpack according to different lengths. 

            if length == 2: 

                format = '%sh' % self.endian 

                # Set the class attribute. 

                setattr(self, name, unpack(format, string)[0]) 

            # Update: Seems to be correct. Two's complement integers seem to be 

            # the common way to store integer values. 

            elif length == 4: 

                format = '%si' % self.endian 

                # Set the class attribute. 

                setattr(self, name, unpack(format, string)[0]) 

            # The other value are the unassigned values. As it is unclear how 

            # these are formated they will be stored as strings. 

            elif name.startswith('unassigned'): 

                # These are only the unassigned fields. 

                format = 'h' * (length / 2) 

                # Set the class attribute. 

                setattr(self, name, string) 

            # Should not happen. 

            else: 

                raise Exception 

 

    def __str__(self): 

        """ 

        Convenience method to print the binary file header. 

        """ 

        final_str = ["Binary File Header:"] 

        for item in BINARY_FILE_HEADER_FORMAT: 

            final_str.append("\t%s: %s" % (item[1], 

                                           str(getattr(self, item[1])))) 

        return "\n".join(final_str) 

 

    def write(self, file, endian=None): 

        """ 

        Writes the header to an open file like object. 

        """ 

        if endian is None: 

            endian = self.endian 

        for item in BINARY_FILE_HEADER_FORMAT: 

            length, name, _ = item 

            # Unpack according to different lengths. 

            if length == 2: 

                format = '%sh' % endian 

                # Write to file. 

                file.write(pack(format, getattr(self, name))) 

            # Update: Seems to be correct. Two's complement integers seem to be 

            # the common way to store integer values. 

            elif length == 4: 

                format = '%si' % endian 

                # Write to file. 

                file.write(pack(format, getattr(self, name))) 

            # These are the two unassigned values in the binary file header. 

            elif name.startswith('unassigned'): 

                temp = '%s' % str(getattr(self, name)) 

                temp_length = len(temp) 

                # Pad to desired length if necessary. 

                if temp_length != length: 

                    temp += '\x00' * (length - temp_length) 

                file.write(temp) 

            # Should not happen. 

            else: 

                raise Exception 

 

    def _createEmptyBinaryFileHeader(self): 

        """ 

        Just fills all necessary class attributes with zero. 

        """ 

        for item in BINARY_FILE_HEADER_FORMAT: 

            _, name, _ = item 

            setattr(self, name, 0) 

 

 

class SEGYTrace(object): 

    """ 

    Convenience class that internally handles a single SEG Y trace. 

    """ 

    def __init__(self, file=None, data_encoding=4, endian='>', 

                 unpack_headers=False, filesize=None, headonly=False): 

        """ 

        Convenience class that internally handles a single SEG Y trace. 

 

        :param file: Open file like object with the file pointer of the 

            beginning of a trace. If it is None, an empty trace will be 

            created. 

        :param data_encoding: The data sample format code as defined in the 

            binary file header: 

 

                1: 

                    4 byte IBM floating point 

                2: 

                    4 byte Integer, two's complement 

                3: 

                    2 byte Integer, two's complement 

                4: 

                    4 byte Fixed point with gain 

                5: 

                    4 byte IEEE floating point 

                8: 

                    1 byte Integer, two's complement 

 

            Defaults to 4. 

        :param big_endian: Bool. True means the header is encoded in big endian 

            and False corresponds to a little endian header. 

        :param unpack_headers: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. Defaults to False. 

        :param filesize: Integer. Filesize of the file. If not given it will be 

            determined using fstat which is slow. 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be read and unpacked. Has a huge impact on memory 

            usage. Data can be read and unpacked on-the-fly after reading the 

            file. Defaults to False. 

        """ 

        self.endian = endian 

        self.data_encoding = data_encoding 

        # If None just return empty structure. 

        if file is None: 

            self._createEmptyTrace() 

            return 

        self.file = file 

        # Set the filesize if necessary. 

        if filesize: 

            self.filesize = filesize 

        else: 

            if isinstance(self.file, StringIO.StringIO): 

                self.filesize = self.file.len 

            else: 

                self.filesize = os.fstat(self.file.fileno())[6] 

        # Otherwise read the file. 

        self._readTrace(unpack_headers=unpack_headers, headonly=headonly) 

 

    def _readTrace(self, unpack_headers=False, headonly=False): 

        """ 

        Reads the complete next header starting at the file pointer at 

        self.file. 

 

        :param unpack_headers: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. Defaults to False. 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be read and unpacked. Has a huge impact on memory 

            usage. Data can be read and unpacked on-the-fly after reading the 

            file. Defaults to False. 

        """ 

        trace_header = self.file.read(240) 

        # Check if it is smaller than 240 byte. 

        if len(trace_header) != 240: 

            msg = 'The trace header needs to be 240 bytes long' 

            raise SEGYTraceHeaderTooSmallError(msg) 

        self.header = SEGYTraceHeader(trace_header, 

                                      endian=self.endian, 

                                      unpack_headers=unpack_headers) 

        # The number of samples in the current trace. 

        npts = self.header.number_of_samples_in_this_trace 

        self.npts = npts 

        # Do a sanity check if there is enough data left. 

        pos = self.file.tell() 

        data_left = self.filesize - pos 

        data_needed = DATA_SAMPLE_FORMAT_SAMPLE_SIZE[self.data_encoding] * \ 

                      npts 

        if npts < 1 or data_needed > data_left: 

            msg = """ 

                  Too little data left in the file to unpack it according to 

                  its trace header. This is most likely either due to a wrong 

                  byteorder or a corrupt file. 

                  """.strip() 

            raise SEGYTraceReadingError(msg) 

        if headonly: 

            # skip reading the data, but still advance the file 

            self.file.seek(data_needed, 1) 

            # build a function for reading data from the disk on the fly 

            self.unpack_data = OnTheFlyDataUnpacker( 

                DATA_SAMPLE_FORMAT_UNPACK_FUNCTIONS[self.data_encoding], 

                self.file.name, self.file.mode, pos, npts, endian=self.endian) 

        else: 

            # Unpack the data. 

            self.data = DATA_SAMPLE_FORMAT_UNPACK_FUNCTIONS[\ 

                    self.data_encoding](self.file, npts, endian=self.endian) 

 

    def write(self, file, data_encoding=None, endian=None): 

        """ 

        Writes the Trace to a file like object. 

 

        If endian or data_encoding is set, these values will be enforced. 

        Otherwise use the values of the SEGYTrace object. 

        """ 

        # Set the data length in the header before writing it. 

        self.header.number_of_samples_in_this_trace = len(self.data) 

 

        # Write the header. 

        self.header.write(file, endian=endian) 

        if data_encoding is None: 

            data_encoding = self.data_encoding 

        if endian is None: 

            endian = self.endian 

        # Write the data. 

        if self.data is None: 

            msg = "No data in the SEGYTrace." 

            raise SEGYWritingError(msg) 

        DATA_SAMPLE_FORMAT_PACK_FUNCTIONS[data_encoding](file, self.data, 

                                                  endian=endian) 

 

    def _createEmptyTrace(self): 

        """ 

        Creates an empty trace with an empty header. 

        """ 

        self.data = np.zeros(0, dtype='float32') 

        self.header = SEGYTraceHeader(header=None, endian=self.endian) 

 

    def __str__(self): 

        """ 

        Print some information about the trace. 

        """ 

        ret_val = 'Trace sequence number within line: %i\n' % \ 

                self.header.trace_sequence_number_within_line 

        ret_val += '%i samples, dtype=%s, %.2f Hz' % (len(self.data), 

                self.data.dtype, 1.0 / \ 

                (self.header.sample_interval_in_ms_for_this_trace / \ 

                float(1E6))) 

        return ret_val 

 

    def __getattr__(self, name): 

        """ 

        This method is only called if the attribute is not found in the usual 

        places (i.e. not an instance attribute or not found in the class tree 

        for self). 

        """ 

        if name == 'data': 

            # Use data unpack function to unpack data on the fly 

            if hasattr(self, 'unpack_data'): 

                return self.unpack_data() 

            else: 

                msg = """ 

                      Attempted to unpack trace data on the fly with 

                      self.unpack_data(), but function does not exist. 

                      """.strip() 

                raise SEGYTraceOnTheFlyDataUnpackingError(msg) 

        else: 

            msg = "'%s' object has no attribute '%s'" % \ 

                  (self.__class__.__name__, name) 

            raise AttributeError(msg) 

 

 

class SEGYTraceHeader(object): 

    """ 

    Convenience class that handles reading and writing of the trace headers. 

    """ 

    def __init__(self, header=None, endian='>', unpack_headers=False): 

        """ 

        Will take the 240 byte of the trace header and unpack all values with 

        the given endianness. 

 

        :param header: String that contains the packed binary header values. 

            If header is None, a trace header with all values set to 0 will be 

            created 

        :param big_endian: Bool. True means the header is encoded in big endian 

            and False corresponds to a little endian header. 

        :param unpack_headers: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. Defaults to False. 

        """ 

        self.endian = endian 

        if header is None: 

            self._createEmptyTraceHeader() 

            return 

        # Check the length of the string, 

        if len(header) != 240: 

            msg = 'The trace header needs to be 240 bytes long' 

            raise SEGYTraceHeaderTooSmallError(msg) 

        # Either unpack the header or just append the unpacked header. This is 

        # much faster and can later be unpacked on the fly. 

        if not unpack_headers: 

            self.unpacked_header = header 

            # The number of samples is an essential information that always 

            # needs to be unpacked. 

            format = '%sH' % self.endian 

        else: 

            self.unpacked_header = None 

            self._readTraceHeader(header) 

 

    def _readTraceHeader(self, header): 

        """ 

        Reads the 240 byte long header and unpacks all values into 

        corresponding class attributes. 

        """ 

        # Set the start position. 

        pos = 0 

        # Loop over all items in the TRACE_HEADER_FORMAT list which is supposed 

        # to be in the correct order. 

        for item in TRACE_HEADER_FORMAT: 

            length, name, special_format, _ = item 

            string = header[pos: pos + length] 

            pos += length 

            setattr(self, name, unpack_header_value(self.endian, string, 

                                                    length, special_format)) 

 

    def write(self, file, endian=None): 

        """ 

        Writes the header to an open file like object. 

        """ 

        if endian is None: 

            endian = self.endian 

        for item in TRACE_HEADER_FORMAT: 

            length, name, special_format, _ = item 

            # Use special format if necessary. 

            if special_format: 

                format = '%s%s' % (endian, special_format) 

                file.write(pack(format, getattr(self, name))) 

            # Pack according to different lengths. 

            elif length == 2: 

                format = '%sh' % endian 

                file.write(pack(format, getattr(self, name))) 

            # Update: Seems to be correct. Two's complement integers seem to be 

            # the common way to store integer values. 

            elif length == 4: 

                format = '%si' % endian 

                file.write(pack(format, getattr(self, name))) 

            # Just the one unassigned field. 

            elif length == 8: 

                field = getattr(self, name) 

                # An empty field will have a zero. 

                if field == 0: 

                    field = 2 * pack('%si' % endian, 0) 

                file.write(field) 

            # Should not happen. 

            else: 

                raise Exception 

 

    def __getattr__(self, name): 

        """ 

        This method is only called if the attribute is not found in the usual 

        places (i.e. not an instance attribute or not found in the class tree 

        for self). 

        """ 

        try: 

            index = TRACE_HEADER_KEYS.index(name) 

        # If not found raise an attribute error. 

        except ValueError: 

            msg = "'%s' object has no attribute '%s'" % \ 

                (self.__class__.__name__, name) 

            raise AttributeError(msg) 

        # Unpack the one value and set the class attribute so it will does not 

        # have to unpacked again if accessed in the future. 

        length, name, special_format, start = TRACE_HEADER_FORMAT[index] 

        string = self.unpacked_header[start: start + length] 

        attribute = unpack_header_value(self.endian, string, length, 

                                        special_format) 

        setattr(self, name, attribute) 

        return attribute 

 

    def __str__(self): 

        """ 

        Just returns all header values. 

        """ 

        retval = '' 

        for item in TRACE_HEADER_FORMAT: 

            _, name, _, _ = item 

            # Do not print the unassigned value. 

            if name == 'unassigned': 

                continue 

            retval += '%s: %i\n' % (name, getattr(self, name)) 

        return retval 

 

    def _createEmptyTraceHeader(self): 

        """ 

        Init the trace header with zeros. 

        """ 

        # First set all fields to zero. 

        for field in TRACE_HEADER_FORMAT: 

            setattr(self, field[1], 0) 

 

 

def readSEGY(file, endian=None, textual_header_encoding=None, 

             unpack_headers=False, headonly=False): 

    """ 

    Reads a SEG Y file and returns a SEGYFile object. 

 

    :param file: Open file like object or a string which will be assumed to be 

        a filename. 

    :param endian: String that determines the endianness of the file. Either 

        '>' for big endian or '<' for little endian. If it is None, obspy.segy 

        will try to autodetect the endianness. The endianness is always valid 

        for the whole file. 

    :param textual_header_encoding: The encoding of the textual header. 

        Either 'EBCDIC', 'ASCII' or None. If it is None, autodetection will 

        be attempted. 

    :param unpack_headers: Bool. Determines whether or not all headers will be 

        unpacked during reading the file. Has a huge impact on the memory usage 

        and the performance. They can be unpacked on-the-fly after being read. 

        Defaults to False. 

    :param headonly: Bool. Determines whether or not the actual data 

        records will be read and unpacked. Has a huge impact on memory usage. 

        Data can be read and unpacked on-the-fly after reading the file. 

        Defaults to False. 

    """ 

    # Open the file if it is not a file like object. 

    if not hasattr(file, 'read') or not hasattr(file, 'tell') or not \ 

        hasattr(file, 'seek'): 

        with open(file, 'rb') as open_file: 

            return _readSEGY(open_file, endian=endian, 

                             textual_header_encoding=textual_header_encoding, 

                             unpack_headers=unpack_headers, headonly=headonly) 

    # Otherwise just read it. 

    return _readSEGY(file, endian=endian, 

                     textual_header_encoding=textual_header_encoding, 

                     unpack_headers=unpack_headers, headonly=headonly) 

 

 

def _readSEGY(file, endian=None, textual_header_encoding=None, 

              unpack_headers=False, headonly=False): 

    """ 

    Reads on open file object and returns a SEGYFile object. 

 

    :param file: Open file like object. 

    :param endian: String that determines the endianness of the file. Either 

        '>' for big endian or '<' for little endian. If it is None, obspy.segy 

        will try to autodetect the endianness. The endianness is always valid 

        for the whole file. 

    :param textual_header_encoding: The encoding of the textual header. 

        Either 'EBCDIC', 'ASCII' or None. If it is None, autodetection will 

        be attempted. 

    :param unpack_headers: Bool. Determines whether or not all headers will be 

        unpacked during reading the file. Has a huge impact on the memory usage 

        and the performance. They can be unpacked on-the-fly after being read. 

        Defaults to False. 

    :param headonly: Bool. Determines whether or not the actual data 

        records will be read and unpacked. Has a huge impact on memory usage. 

        Data can be read and unpacked on-the-fly after reading the file. 

        Defaults to False. 

    """ 

    return SEGYFile(file, endian=endian, 

                    textual_header_encoding=textual_header_encoding, 

                    unpack_headers=unpack_headers, headonly=headonly) 

 

 

class SUFile(object): 

    """ 

    Convenience class that internally handles Seismic Unix data files. It 

    currently can only read IEEE 4 byte float encoded SU data files. 

    """ 

    def __init__(self, file=None, endian=None, unpack_headers=False, 

                 headonly=False): 

        """ 

        :param file: A file like object with the file pointer set at the 

            beginning of the SEG Y file. If file is None, an empty SEGYFile 

            object will be initialized. 

 

        :param endian: The endianness of the file. If None, autodetection will 

            be used. 

        :param unpack_header: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. 

            Defaults to False. 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be read and unpacked. Has a huge impact on memory 

            usage. Data can be read and unpacked on-the-fly after reading the 

            file. Defaults to False. 

        """ 

        if file is None: 

            self._createEmptySUFileObject() 

            return 

            # Set the endianness to big. 

            if endian is None: 

                self.endian = '>' 

            else: 

                self.endian = ENDIAN[endian] 

            return 

        self.file = file 

        # If endian is None autodetect is. 

        if not endian: 

            self._autodetectEndianness() 

        else: 

            self.endian = ENDIAN[endian] 

        # Read the actual traces. 

        self._readTraces(unpack_headers=unpack_headers, headonly=headonly) 

 

    def _autodetectEndianness(self): 

        """ 

        Tries to automatically determine the endianness of the file at hand. 

        """ 

        self.endian = autodetectEndianAndSanityCheckSU(self.file) 

        if self.endian is False: 

            msg = 'Autodetection of Endianness failed. Please specify it ' + \ 

                  'by hand or contact the developers.' 

            raise Exception(msg) 

 

    def _createEmptySUFileObject(self): 

        """ 

        Creates an empty SUFile object. 

        """ 

        self.traces = [] 

 

    def __str__(self): 

        """ 

        Prints some information about the SU file. 

        """ 

        return '%i traces in the SU structure.' % len(self.traces) 

 

    def _readTraces(self, unpack_headers=False, headonly=False): 

        """ 

        Reads the actual traces starting at the current file pointer position 

        to the end of the file. 

 

        :param unpack_header: Bool. Determines whether or not all headers will 

            be unpacked during reading the file. Has a huge impact on the 

            memory usage and the performance. They can be unpacked on-the-fly 

            after being read. 

            Defaults to False. 

        :param headonly: Bool. Determines whether or not the actual data 

            records will be unpacked. Useful if one is just interested in the 

            headers.  Defaults to False. 

        """ 

        self.traces = [] 

        # Big loop to read all data traces. 

        while True: 

            # Read and as soon as the trace header is too small abort. 

            try: 

                # Always unpack with IEEE 

                trace = SEGYTrace(self.file, 5, self.endian, 

                                  unpack_headers=unpack_headers, 

                                  headonly=headonly) 

                self.traces.append(trace) 

            except SEGYTraceHeaderTooSmallError: 

                break 

 

    def write(self, file, endian=None): 

        """ 

        Write a SU Y file to file which is either a file like object with a 

        write method or a filename string. 

 

        If endian is set it will be enforced. 

        """ 

        if not hasattr(file, 'write'): 

            with open(file, 'wb') as file: 

                self._write(file, endian=endian) 

            return 

        self._write(file, endian=endian) 

 

    def _write(self, file, endian=None): 

        """ 

        Write a SU Y file to file which is either a file like object with a 

        write method or a filename string. 

 

        If endian is set it will be enforced. 

        """ 

        # Write all traces. 

        for trace in self.traces: 

            trace.write(file, data_encoding=5, endian=endian) 

 

 

def readSU(file, endian=None, unpack_headers=False, headonly=False): 

    """ 

    Reads a Seismic Unix (SU) file and returns a SUFile object. 

 

    :param file: Open file like object or a string which will be assumed to be 

        a filename. 

    :param endian: String that determines the endianness of the file. Either 

        '>' for big endian or '<' for little endian. If it is None, obspy.segy 

        will try to autodetect the endianness. The endianness is always valid 

        for the whole file. 

    :param unpack_header: Bool. Determines whether or not all headers will be 

        unpacked during reading the file. Has a huge impact on the memory usage 

        and the performance. They can be unpacked on-the-fly after being read. 

        Defaults to False. 

    :param headonly: Bool. Determines whether or not the actual data records 

        will be unpacked. Useful if one is just interested in the headers. 

        Defaults to False. 

    """ 

    # Open the file if it is not a file like object. 

    if not hasattr(file, 'read') or not hasattr(file, 'tell') or not \ 

        hasattr(file, 'seek'): 

        with open(file, 'rb') as open_file: 

            return _readSU(open_file, endian=endian, 

                           unpack_headers=unpack_headers, headonly=headonly) 

    # Otherwise just read it. 

    return _readSU(file, endian=endian, unpack_headers=unpack_headers, 

                   headonly=headonly) 

 

 

def _readSU(file, endian=None, unpack_headers=False, headonly=False): 

    """ 

    Reads on open file object and returns a SUFile object. 

 

    :param file: Open file like object. 

    :param endian: String that determines the endianness of the file. Either 

        '>' for big endian or '<' for little endian. If it is None, obspy.segy 

        will try to autodetect the endianness. The endianness is always valid 

        for the whole file. 

    :param unpack_header: Bool. Determines whether or not all headers will be 

        unpacked during reading the file. Has a huge impact on the memory usage 

        and the performance. They can be unpacked on-the-fly after being read. 

        Defaults to False. 

    :param headonly: Bool. Determines whether or not the actual data records 

        will be unpacked. Useful if one is just interested in the headers. 

        Defaults to False. 

    """ 

    return SUFile(file, endian=endian, unpack_headers=unpack_headers, 

                  headonly=headonly) 

 

 

def autodetectEndianAndSanityCheckSU(file): 

    """ 

    Takes an open file and tries to determine the endianness of a Seismic 

    Unix data file by doing some sanity checks with the unpacked header values. 

 

    Returns False if the sanity checks failed and the endianness otherwise. 

 

    It is assumed that the data is written as 32bit IEEE floating points in 

    either little or big endian. 

 

    The test currently can only identify SU files in which all traces have the 

    same length. It basically just makes a sanity check for various fields in 

    the Trace header. 

    """ 

    pos = file.tell() 

    if isinstance(file, StringIO.StringIO): 

        size = file.len 

    else: 

        size = os.fstat(file.fileno())[6] 

    if size < 244: 

        return False 

    # Also has to be a multiple of 4 in length because every header is 400 long 

    # and every data value 4 byte long. 

    elif (size % 4) != 0: 

        return False 

    # Jump to the number of samples field in the trace header. 

    file.seek(114, 0) 

    sample_count = file.read(2) 

    interval = file.read(2) 

    # Jump to the beginning of the year fields. 

    file.seek(156, 0) 

    year = file.read(2) 

    jul_day = file.read(2) 

    hour = file.read(2) 

    minute = file.read(2) 

    second = file.read(2) 

    # Jump to previous position. 

    file.seek(pos, 0) 

    # Unpack in little and big endian. 

    le_sample_count = unpack('<h', sample_count)[0] 

    be_sample_count = unpack('>h', sample_count)[0] 

    # Check if both work. 

    working_byteorders = [] 

    if le_sample_count > 0: 

        length = 240 + (le_sample_count * 4) 

        if (size % length) == 0: 

            working_byteorders.append('<') 

    if be_sample_count > 0: 

        length = 240 + (be_sample_count * 4) 

        if (size % length) == 0: 

            working_byteorders.append('>') 

    # If None works return False. 

    if len(working_byteorders) == 0: 

        return False 

    # Check if the other header values make sense. 

    still_working_byteorders = [] 

    for bo in working_byteorders: 

        this_interval = unpack('%sh' % bo, interval)[0] 

        this_year = unpack('%sh' % bo, year)[0] 

        this_julday = unpack('%sh' % bo, jul_day)[0] 

        this_hour = unpack('%sh' % bo, hour)[0] 

        this_minute = unpack('%sh' % bo, minute)[0] 

        this_second = unpack('%sh' % bo, second)[0] 

        # Make a sanity check for each. 

        # XXX: The arbitrary maximum of the sample interval is 10 seconds. 

        if this_interval <= 0 or this_interval > 10E7: 

            continue 

        # Some programs write two digit years. 

        if this_year != 0 and (this_year < 1930 or this_year >= 2030) and \ 

            (this_year < 0 or this_year >= 100): 

            continue 

        # 9999 is often used as a placeholder 

        if (this_julday > 366 or this_julday < 0) and this_julday != 9999: 

            continue 

        if this_hour > 24 or this_hour < 0: 

            continue 

        if this_minute > 60 or this_minute < 0: 

            continue 

        if this_second > 60 or this_second < 0: 

            continue 

        still_working_byteorders.append(bo) 

    length = len(still_working_byteorders) 

    if not length: 

        return False 

    elif length == 1: 

        return still_working_byteorders[0] 

    else: 

        # XXX: In the unlikely case both byteorders pass the sanity checks 

        # something else should be checked. Currently it is not. 

        msg = """ 

            Both possible byteorders passed all sanity checks. Please contact 

            the ObsPy developers so they can implement additional tests. 

            """.strip() 

        raise Exception(msg)