V1 Api Overview

Create Seismic API client

Before we can do anything, we need to create a client that can interact with the Cognite Seismic API. The client is authenticated either using an API key for legacy auth mode:

[1]:
import os
import getpass
from cognite.seismic import CogniteSeismicClient

base_url = "https://greenfield.cognitedata.com/"
project = "seismic-test" # Specify project name here

api_key = os.environ.get("COGNITE_API_KEY")
if api_key is None:
    api_key = getpass.getpass("Enter your Cognite API key:")

client = CogniteSeismicClient(
    api_key=api_key,
    base_url=base_url,
    project=project,
)

or using OpenID Connect (OIDC):

[2]:
import msal

tenant_id = "specify your OIDC tenant here"
client_id = "Specify your OIDC client id here"

def login_and_get_token_supplier(base_url):
    authority = "https://login.microsoftonline.com/" + tenant_id
    app = msal.PublicClientApplication(client_id=client_id, authority=authority)
    scopes = [base_url + ".default"]
    app.acquire_token_interactive(scopes=scopes)
    try:
        account = app.get_accounts()[0]
    except IndexError:
        raise Exception("No accounts returned after Azure AD login. Check permissions?")
    print(f"Logged in as {account['username']}")

    def tokenmaker():
        return app.acquire_token_silent(account=account, scopes=scopes)["access_token"]

    return tokenmaker

tokenmaker = login_and_get_token_supplier(base_url)
client = CogniteSeismicClient(base_url=base_url, oidc_token=tokenmaker, project=project)

Surveys

Surveys serve as the basic unit of organization for seismic data.

Create a survey

Before registering a Segy file, we first need to create a survey to act as a container for the Segy file.

[3]:
survey_name = "my_new_survey"
crs = "EPSG:23031"
[4]:
from pprint import pprint
from cognite.seismic.data_classes.errors import AlreadyExistsError
try:
    # Register our survey. If it already exists, an exception will be thrown.
    my_survey = client.survey.create(name=survey_name, external_id=survey_name, crs=crs)
except AlreadyExistsError as e:
    # Fetch the already existing survey
    my_survey = client.survey.get(name=survey_name)

pprint(my_survey)
[4]:
Survey(id=4044669010395434,
       external_id='my_new_survey',
       name='my_new_survey',
       seismic_ids=[],
       metadata={})

[5]:
# We can also attach metadata to a survey when registering it.
name = survey_name + "_meta"
metadata = {"area": "B13", "processed": "False"}
try:
    my_survey_2 = client.survey.create(name=name, external_id=name, crs=crs, metadata=metadata)
except AlreadyExistsError as e:
    my_survey_2 = client.survey.get(name=name)

pprint(my_survey_2)
[5]:
Survey(id=5235100268682392,
       external_id='my_new_survey_meta',
       name='my_new_survey_meta',
       seismic_ids=[],
       metadata={'area': 'B13', 'processed': 'False'})

Retrieve a survey

There are multiple methods for finding an existing survey such as by id, by name, by listing the surveys, or by searching for surveys that intersect with a given coverage.

Retrieve a survey by name

[6]:
my_survey = client.survey.get(name=survey_name)
pprint(my_survey)
[6]:
Survey(id=4044669010395434,
       external_id='my_new_survey',
       name='my_new_survey',
       seismic_ids=[],
       metadata={})

List all available surveys

[7]:
# list all surveys
for survey in client.survey.list():
  pprint(survey)
[7]:
Survey(id=4044669010395434,
       external_id='my_new_survey',
       name='my_new_survey',
       seismic_ids=[],
       metadata={})
Survey(id=5235100268682392,
       external_id='my_new_survey_meta',
       name='my_new_survey_meta',
       seismic_ids=[],
       metadata={})

Files

Seg-Y files are the primary method to ingest seismic data.

Register a file

With a survey assigned, we can register a file to the survey. The file must already be uploaded to gcloud storage before this step.

[8]:
file_name = "test-3d.sgy"
path = "gs://cognite-seismic-eu/tests/" + file_name
external_id = "3d-file"

Now we can register the segy file that we will ingest. The file must be uploaded to gcloud storage before ingestion.

[9]:
try:
    # Similar to surveys, an exception will be thrown if the file already exists
    file = client.file.register(survey_id=my_survey.id, path=path, crs=crs, external_id=external_id)
except AlreadyExistsError as e:
    [file] = client.file.search(name=file_name)
pprint(file)
[9]:
SourceSegyFile(id=7882565489727443,
               external_id='3d-file',
               name='test-3d.sgy',
               cloud_storage_path='gs://cognite-seismic-eu/tests/',
               metadata={},
               segy_overrides=SegyOverrides(energy_source_point_offset=None,
                                            cdp_number_offset=None,
                                            inline_offset=None,
                                            crossline_offset=None,
                                            cdp_x_offset=None,
                                            cdp_y_offset=None,
                                            shotpoint_offset=None,
                                            source_group_scalar_override=None),
               key_fields=[TraceHeaderField.INLINE,
                           TraceHeaderField.CROSSLINE],
               dimensions=3)

If the file is a 2D seismic file, pass 2 for the number of dimensions and specify which trace headers to index traces by:

[10]:
from cognite.seismic import TraceHeaderField
file_name="test-2d.sgy"
path = "gs://cognite-seismic-eu/tests/" + file_nameexternal_id="2d-file"

file = client.file.register(
    survey_external_id="my_new_survey_meta",
    path=path,
    crs=crs,
    external_id=external_id,
    dimensions=2,
    key_fields=[
        # This field will always be included even if omitted from this list
        TraceHeaderField.CDP,
        # These optional headers usually mean the same thing, but correspond to different
        # header offsets in the Seg-Y version 2 specification
        TraceHeaderField.Shotpoint,
        TraceHeaderField.EnergySourcePoint,
    ],
)
[10]:
SourceSegyFile(id=7715308426927703,
               external_id='2d-file',
               name='test-2d.sgy',
               cloud_storage_path='gs://cognite-seismic-eu/tests/',
               metadata={},
               segy_overrides=SegyOverrides(energy_source_point_offset=None,
                                            cdp_number_offset=None,
                                            inline_offset=None,
                                            crossline_offset=None,
                                            cdp_x_offset=None,
                                            cdp_y_offset=None,
                                            shotpoint_offset=None,
                                            source_group_scalar_override=None),
               key_fields=[TraceHeaderField.ENERGY_SOURCE_POINT,
                           TraceHeaderField.CDP,
                           TraceHeaderField.SHOTPOINT],
               dimensions=2)

Retrieve a file

Similar to surveys, we can find an existing file by its unique id, or its name.

[11]:
# By numeric id
file = client.file.get(id=7882565489727443)
# or by external id
file = client.file.get(external_id="3d-file")
# or by the old uuid format
file = client.file.get(uuid="41453b65-f6ff-45f9-8335-1fe87d36840d")
print(f"uuid: {file.uuid}")
pprint(file)
[11]:
uuid: 41453b65-f6ff-45f9-8335-1fe87d36840d
SourceSegyFile(id=7882565489727443,
               external_id='3d-file',
               name='test-3d.sgy',
               cloud_storage_path='gs://cognite-seismic-eu/tests/',
               metadata={},
               segy_overrides=SegyOverrides(energy_source_point_offset=None,
                                            cdp_number_offset=None,
                                            inline_offset=None,
                                            crossline_offset=None,
                                            cdp_x_offset=None,
                                            cdp_y_offset=None,
                                            shotpoint_offset=None,
                                            source_group_scalar_override=None),
               key_fields=[TraceHeaderField.INLINE,
                           TraceHeaderField.CROSSLINE],
               dimensions=3)

We can also list all files that are visible in the project.

[12]:
for file in client.file.list():
    pprint(file)
[12]:
SourceSegyFile(id=7882565489727443,
               external_id='3d-file',
               name='test-3d.sgy',
               cloud_storage_path='gs://cognite-seismic-eu/tests/',
               metadata={},
               segy_overrides=SegyOverrides(energy_source_point_offset=None,
                                            cdp_number_offset=None,
                                            inline_offset=None,
                                            crossline_offset=None,
                                            cdp_x_offset=None,
                                            cdp_y_offset=None,
                                            shotpoint_offset=None,
                                            source_group_scalar_override=None),
               key_fields=[TraceHeaderField.INLINE,
                           TraceHeaderField.CROSSLINE],
               dimensions=3)
SourceSegyFile(id=7715308426927703,
               external_id='2d-file',
               name='test-2d.sgy',
               cloud_storage_path='gs://cognite-seismic-eu/tests/',
               metadata={},
               segy_overrides=SegyOverrides(energy_source_point_offset=None,
                                            cdp_number_offset=None,
                                            inline_offset=None,
                                            crossline_offset=None,
                                            cdp_x_offset=None,
                                            cdp_y_offset=None,
                                            shotpoint_offset=None,
                                            source_group_scalar_override=None),
               key_fields=[TraceHeaderField.CDP,
                           TraceHeaderField.SHOTPOINT,
                           TraceHeaderField.ENERGY_SOURCE_POINT],
               dimensions=2)

Ingest a file

With the file registered to our survey, we can now trigger an ingestion job which will get queued in the background.

[13]:
ingestion_job = client.file.ingest(file_external_id="3d-file", storage_tier="tier2_cloudstorage")
pprint(ingestion_job)
[13]:
IngestionJob(job_id='cfda9d0f-2267-4768-9252-2cfd1ae8c5be')

Ingestion job status

We can retrieve information about ingestion jobs with client.job.status. This API takes a number of search criteria and returns all relevant jobs as a stream in order of when they were last updated by the ingestion worker.

Below are a number of examples of how to use this API to retrieve jobs that match specific criteria. For full details of what can be specified, please see the v1 API documentation.

[14]:
# Return a specific job (will always be 1 for a job_id)
for job in client.job.status(job_id = ingestion_job.job_id):
  pprint(job.status)
  # Print all logs for this job (if any)
  for log in job.logs:
    pprint(log)
[14]:
<StatusCode.QUEUED: 1>

[15]:
# Periodically check status until complete
import time
from cognite.seismic.data_classes.api_types import StatusCode
check = list(client.job.status(job_id=ingestion_job.job_id))[0]

while (check.status != StatusCode.SUCCESS):
    time.sleep(5)
    check = list(client.job.status(job_id=ingestion_job.job_id))[0]
    pprint(check.status)

for log in check.logs:
    pprint(log)
[15]:
<StatusCode.IN_PROGRESS: 2>
(...)
<StatusCode.IN_PROGRESS: 2>
<StatusCode.SUCCESS: 3>
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 57, 9),
             log_line='Starting ingestion for file_id '
                      '"41453b65-f6ff-45f9-8335-1fe87d36840d"')
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 57, 9),
             log_line='Retrieving binary header')
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 57, 9),
             log_line='Retrieving text header')
(...)
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 58, 18),
             log_line='Publishing seismic store with id 6201752806813955')
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 58, 18),
             log_line='Ingestion complete')

[16]:
# Retrieve all jobs for the given file
for job in client.job.status(file_uuid="41453b65-f6ff-45f9-8335-1fe87d36840d"):
  pprint(job)
[16]:
JobStatus(job_id='7b738cff-d7ef-441f-8f66-3c8bf71b0152',
          file_uuid='41453b65-f6ff-45f9-8335-1fe87d36840d',
          status=<StatusCode.SUCCESS: 3>,
          target_storage_tier_name='tier2_cloudstorage',
          started_at=datetime.datetime(2022, 4, 11, 10, 38, 8),
          updated_at=datetime.datetime(2022, 4, 11, 10, 42, 16),
          logs=[])
JobStatus(job_id='e12b5029-a490-4a7f-8665-dfff8bfe3169',
          file_uuid='41453b65-f6ff-45f9-8335-1fe87d36840d',
          status=<StatusCode.SUCCESS: 3>,
          target_storage_tier_name='tier2_cloudstorage',
          started_at=datetime.datetime(2022, 4, 11, 11, 12, 16),
          updated_at=datetime.datetime(2022, 4, 11, 11, 17, 32),
          logs=[])

[17]:
# Retrieve all jobs updated in the last 10 minutes
from datetime import datetime, timedelta
last_10_minutes = datetime.now() - timedelta(minutes=10)
for job in client.job.status(updated_after=last_10_minutes):
  pprint(job.logs[-1])
[17]:
JobStatusLog(timestamp=datetime.datetime(2022, 6, 29, 10, 58, 18),
             log_line='Ingestion complete')

[18]:
# Retrieve all failed jobs in the last 24 hours
from datetime import datetime, timedelta
from cognite.seismic.data_classes.api_types import StatusCode
last_24_hours = datetime.now() - timedelta(days=1)

any_failed = False
for job in client.job.status(updated_after=last_24_hours, status=StatusCode.FAILED):
  any_failed = True
  pprint(job)

if not any_failed:
    print("No failed jobs")
[18]:
No failed jobs

[19]:
# Retrieve all failed jobs for files using tier 1 storage in the last week
last_week = datetime.now() - timedelta(weeks=1)

any_failed = False
for job in client.job.status(target_storage_tier_name="tier1_bigtable", status=StatusCode.FAILED, updated_after=last_week):
  any_failed = True
  pprint(job)

if not any_failed:
    print("No failed jobs")
[19]:
No failed jobs

Seismic Stores

Once a file has been ingested, a seismic store is created from that data. The seismic store represents the actual trace data that lives in the service.

Listing

[20]:
for store in client.seismicstore.list():
  pprint(store)
[20]:
SeismicStore(id=205818405704076,
             name='test-3d.sgy',
             survey_uuid='ef64f24e-d56a-4ad2-8c85-9e3b3c097c09',
             ingestion_source=1,
             metadata={},
             storage_tier_name=[],
             trace_header_fields=[TraceHeaderField.INLINE,
                                  TraceHeaderField.CROSSLINE],
             dimensions=3)
SeismicStore(id=247177488956856,
             name='test-2d.sgy',
             survey_uuid='e494af1d-0bc2-4ff3-83df-b14c40b0f513',
             ingestion_source=1,
             metadata={},
             storage_tier_name=[],
             trace_header_fields=[TraceHeaderField.ENERGY_SOURCE_POINT,
                                  TraceHeaderField.CDP,
                                  TraceHeaderField.SHOTPOINT],
             dimensions=2)

Searching for seismic stores

We can find the seismicstore corresponding to a given ingested file using the search command:

[21]:
from cognite.seismic import SearchSpecFile

[store] = client.seismicstore.search(search_spec=SearchSpecFile(external_id="3d-file"))

Get metadata/header information about a seismic store

[22]:
# list() and search() does not include all data, so we fetch the specific id we are interested in
store = client.seismicstore.get(id=store.id)

print("Binary header:")
pprint(store.binary_header)
print("")

print("Raw binary header length:", len(store.binary_header.raw_header))
print("")

print("Text header:")
print(store.text_header.header)
[22]:
Binary header:
BinaryHeader(traces=12018301,
             trace_data_type=5,
             fixed_length_traces=0,
             segy_revision=0,
             auxtraces=0,
             interval=4000,
             interval_original=0,
             samples=1000,
             samples_original=0,
             ensemble_fold=1,
             vertical_sum=1,
             trace_type_sorting_code=4,
             sweep_type_code=0,
             sweep_frequency_start=0,
             sweep_frequency_end=0,
             sweep_length=0,
             sweep_channel=0,
             sweep_taper_start=0,
             sweep_taper_end=0,
             sweep_taper_type=0,
             correlated_traces=0,
             amplitude_recovery=0,
             original_measurement_system=1,
             impulse_signal_polarity=1,
             vibratory_polarity_code=0)

Raw binary header length: 400

Text header:
C 1 Fake output for the purposes of this notebook
C 2
C 3 Inlines: 1000 - 4000
C 4 Crosslines: 2000 - 7000
C 5
C 6 UTM zone 31 N
C 7 Ellipsoid: ED50
C 8
C 9
C10
C11
C12
C13
C14
C15
C16
C17
C18
C19
C20
C21
C22
C23
C24
C25
C26
C27
C28
C29
C30
C31
C32
C33
C34
C35
C36
C37
C38
C39
C40 END OF EBCDIC

Seismicstore Coverage

Fetch the coverage geometry of the seismicstore in the seismicstore’s native crs

[23]:
store = client.seismicstore.get(id=store.id, coverage_format="wkt") # Can also be returned in 'geojson'

print("Coverage:")
print(f"CRS: {store.coverage.crs}")
print(store.coverage.wkt)
[23]:
Coverage:
CRS: EPSG:23031
POLYGON((508983.7151051879 7061923.063532239,564955.2881576419 7062575.921909288,566109.720059501170061 88.82306361,509141.9386231695 7005527.273434886,508983.7151051879 7061923.063532239))

Alternatively, fetch coverage in your preferred coordinate system, like latitude and longitude

[24]:
store = client.seismicstore.get(id=store.id, coverage_crs="EPSG:4326", coverage_format="wkt")

print("Coverage:")
print(f"CRS: {store.coverage.crs}")
print(store.coverage.wkt)
[24]:
Coverage:
CRS: EPSG:4326
POLYGON((3.1797294950815846 63.68302074216294,4.311321291956585 63.68302074216294,4.311321291956585 63.17688405265282,3.1797294950815846 63.17688405265282,3.1797294950815846 63.68302074216294))

Coverage is also supported for 2D files

[25]:
[store_2d] = client.seismicstore.search(search_spec=SearchSpecFile(external_id="2d-file"))
store_2d = client.seismicstore.get(coverage_crs="EPSG:4326", coverage_format="wkt")

print("2D Coverage:")
print(f"CRS: {store_2d.coverage.crs}")
print(store_2d.coverage.wkt)
[25]:
2D Coverage:
CRS: EPSG:4326
LINESTRING(3.3335380888315846 63.127267174423885,4.344280276331585 63.186797245247405,5.014446291956585 63.399111795082334)

Describe the range of traces contained in the seismic store

[26]:
# For the 3d file
print("3D:")
pprint(client.traces.get_trace_bounds(seismic_store_id=store.id))

# For the 2d file
print("2D:")
pprint(client.traces.get_trace_bounds(seismic_store_id=store_2d.id))
[26]:
3D:
TraceBounds3d(num_traces=12018301,
              sample_count=1000,
              size_kilobytes=21087159,
              crs='EPSG:23031',
              z_range=RangeInclusive(0, 999, 1),
              inline_bounds=RangeInclusive(1000, 4000, 1),
              xline_bounds=RangeInclusive(2000, 7000, 1))
2D:
TraceBounds2d(num_traces=10000,
              sample_count=2000,
              size_kilobytes=45547,
              crs='EPSG:23031',
              z_range=RangeInclusive(0, 1999, 1),
              trace_key_header=TraceHeaderField.CDP,
              trace_key_bounds=RangeInclusive(4500, 14499, 2))

Download Segy file

Download a seismic store as a SEGY file.

[27]:
store = client.seismicstore.get(id=store.id)
file_generator = store.get_segy()
# write sgy file to disk
with open("test-3d.sgy", "wb") as output:
    for f in file_generator:
        output.write(f)

Download Segy file by inline / crossline range

Downloads the seismic store as a SEGY file with only the data inside the range of inlines and crosslines.

[28]:
from cognite.seismic import Seismic3dRect

# Getting line ranges
bounds = client.traces.get_trace_bounds(seismic_store_id=store.id)
inline_range = bounds.inline_bounds
crossline_range = bounds.xline_bounds

print(f"inline: {inline_range}, crossline: {crossline_range}")

cropped_rect = Seismic3dRect(
    inline=(2000, 3000, 10), # Every 10th inline between 2000 and 3000 (inclusive)
    xline=(4000, 5000, 10), # Every 10th xline between 4000 and 5000 (inclusive)
)

print("cropped rectangle:")
pprint(cropped_rect)

file_generator = store.get_segy(extent=cropped_rect)

# write sgy file to disk
with open("test-partial.sgy", "wb") as output:
    for f in file_generator:
        output.write(f)
[28]:
inline: RangeInclusive(1000, 4000, 1), crossline: RangeInclusive(2000, 7000, 1)
cropped rectangle:
Seismic3dRect(inline=RangeInclusive(2000, 3000, 10),
              xline=RangeInclusive(4000, 5000, 10))

Download a 2D Segy file by CDP filter

[29]:
from cognite.seismic import Seismic2dExtent

extent = Seismic2dExtent.cdp_range((5000, 8000, 5)) # Every 5th CDP from 5000 to 8000 (inclusive)
file_generator = store_2d.get_segy(extent=2d_extent)
# write sgy file to disk
with open("test-2d.sgy", "wb") as output:
    for f in file_generator:
        output.write(f)

Download Segy file by Polygon filter

Downloads an existing SEGY file with only the data inside the given 2D polygon.

[30]:
from cognite.seismic import Geometry

wkt_polygon = "POLYGON((\
3.6081962919565846 63.47771399862825,\
3.8718681669565846 63.47771399862825,\
3.8718681669565846 63.369580326166435,\
3.6081962919565846 63.369580326166435,\
3.6081962919565846 63.47771399862825))"

file_generator = store.get_segy(
    geometry = Geometry(
        crs="EPSG:4326",
        wkt=wkt_polygon,
    )
)
# write sgy file to disk
with open("test-geometry.sgy", "wb") as output:
    for f in file_generator:
        output.write(f)

Slices

Seismic slice by volume

Returns the traces of an volume in a given seismic store, specified by inline range, xline range, and (optional) z-range.

[31]:
import itertools

inline_range = (3000, 3200)
xline_range = (4500, 4600)
z_range = (500, 800, 10)

extent = Seismic3dRect(inline=inline_range, xline=xline_range)

# Can get an estimate of the volume size
bounds = client.traces.get_trace_bounds(seismic_store_id=slice_store_id, extent=extent)
print(f"Trace count: {bounds.num_traces}")
print(f"Total data size: {bounds.size_kilobytes}KiB")
print()

stream = client.traces.stream_traces(seismic_store_id=store.id, extent=extent, z_range=z_range)

# Print out the first 2 streamed traces
for trace in itertools.islice(stream, 2):
  pprint(trace)

# Can also put all the data into numpy arrays
array_data = client.traces.get_array(seismic_store_id=store.id, extent=extent, z_range=z_range)
print("")
print("Array Data:")
pprint(array_data)

# Similarly, one can filter 2d files by ranges
# For example, every 5th cdp number from 5000 to 8000, but leaving out those between 6000 and 7000
extent = Seismic2dExtent.cdp_ranges([(5000, 6000, 5), (7000, 8000, 5)])
[31]:
Trace count: 20301
Total data size: 6204KiB

Trace(inline=3000,
      xline=4500,
      cdp=None,
      shotpoint=None,
      energy_source_point=None,
      trace=[-55.28179931640625, -65.46575927734375, 9.487056732177734, -108.53865051269531, -33.991668701171875, 74.35055541992188, -145.4818115234375, 12.349244117736816, -20.685134887695312, 98.94192504882812, 24.077850341796875, -762.764404296875, 152.55291748046875, -554.5380859375, 328.544921875, 121.7352294921875, -441.320556640625, 311.071533203125, 75.46009826660156, 51.340179443359375, -94.68217468261719, -133.82919311523438, -118.41116333007812, 130.63272094726562, 31.378494262695312, 30.02783203125, -113.121337890625, -24.3126220703125, -38.029876708984375, 79.06564331054688, -45.2379150390625],
      coordinate=Coordinate(crs='', x=509595.0, y=7047732.0))
Trace(inline=3001,
      xline=4500,
      cdp=None,
      shotpoint=None,
      energy_source_point=None,
      trace=[-62.72950744628906, -57.79319763183594, 1.8213920593261719, -101.11788940429688, -43.46852111816406, 90.44717407226562, -139.02020263671875, 27.757095336914062, -39.41236877441406, 104.24772644042969, 18.59271240234375, -761.060791015625, 154.1969757080078, -561.129150390625, 343.43359375, 119.17832946777344, -455.101318359375, 327.17236328125, 65.62959289550781, 58.56500244140625, -89.13641357421875, -125.09169006347656, -98.18023681640625, 126.92657470703125, 26.58380126953125, 27.300994873046875, -123.54443359375, -19.713760375976562, -33.656829833984375, 86.3408203125, -34.68218994140625],
      coordinate=Coordinate(crs='', x=509600.0, y=7047720.0))

Array Data:
ArrayData3d(
    trace_data=<array of shape (201, 101, 31)>,
    crs='EPSG:23031',
    coord_x=<array of shape (201, 101)>,
    coord_y=<array of shape (201, 101)>,
    inline_range=RangeInclusive(3000, 3200, 1),
    xline_range=RangeInclusive(4500, 4600, 1),
    z_range=RangeInclusive(500, 800, 10)
)

Partitions

While seismic stores are sufficient for most single-tenant situations, you may find it useful to create views into subsections of seismic data, or even allow limited access to external users that are not internal to your organization. “Seismics” are no-copy subsets of seismic store data that are grouped into partitions.

Partitions are a unit of access. You can assign partitions to service accounts using the Cognite API’s /api/v1/projects/[PROJECT_NAME]/groups endpoint (documented here). Below is a Python example.

[32]:
import requests
import json

url = "https://api.cognitedata.com/api/v1/projects/[PROJECT_NAME]/groups"

# The name of the new group
group_name = json.dumps("my-new-capability-group")

# The seismics to assign to the partition
seismic_ids = json.dumps([14,21])

payload="""
{
    "items": [
        {
            "name": %s,
            "capabilities": [
                {
                    "projectsAcl": {
                        "actions": ["READ"],
                        "scope": { "all": {} }
                    }
                },
                {
                    "seismicAcl": {
                        "actions": ["READ"],
                        "scope": {
                            "partition": {
                                "partition_ids": %s
                            }
                        }
                    }
                },
                {
                    "geospatialAcl": {
                        "actions": [
                            "READ"
                        ],
                        "scope": {
                            "all": {}
                        }
                    }
                }
            ]
        }
    ]
}
""" % (group_name, seismic_ids,)

headers = {
  'api-key': api_key,
  'Content-Type': 'application/json'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)

Creating a partition

[33]:

partition = client.partition.create(external_id="my-partition-external-id", name="optional name") print(partition)
[33]:
Partition(id=4166332971015156, external_id='my-partition-external-id', name='optional name')

Search partitions

[34]:
# by ID
client.partition.search(id=partition.id)
# by external ID
client.partition.search(external_id=partition.external_id)
# by external ID substring
client.partition.search(external_id_substring="external-id")
# by name
client.partition.search(name="optional name")
# by name substring
client.partition.search(name_substring="name")
# list partitions
client.partition.list()
# simply get a partition
client.partition.get(external_id=partition.external_id)
[34]:
Partition(id=4166332971015156, external_id='my-partition-external-id', name='optional name')

Delete partitions

[36]:
client.partition.delete(external_id=partition.external_id)

Seismics

Creating seismics

Seismics are a view into seismic store data. So when creating a seismic, you need to provide the view. The seismic service accepts None (which takes a view on the entire seismic store), a description of which inlines/crosslines to include (using SeismicExtent objects), and polygons (which cut the seismic store).

[37]:
from cognite.seismic import FullCutout

# Entire seismic store
seismic = client.seismic.create(
    external_id="seismic-from-full",
    # Partition identifier can be either an ID (integer) or an external id (string)
    partition_identifier=partition.external_id,
    seismic_store_id=store.id,
    cutout = FullCutout(),
    metadata = {
        "optional": "metadata",
        "other": "metadata",
    },
)
pprint(seismic.extent)

[37]:
Seismic3dDef(major_header=TraceHeaderField.INLINE,
             minor_header=TraceHeaderField.CROSSLINE,
             lines={1000: [RangeInclusive(2000, 4000, 1)],
                    1001: [RangeInclusive(2001, 4001, 1)],
                    (...)
                    3999: [RangeInclusive(4999, 6999, 1)],
                    4000: [RangeInclusive(5000, 7000, 1)]})

[38]:
from cognite.seismic import EmptyCutout

# Empty cutout
seismic = client.seismic.create(
    external_id="seismic-from-empty",
    partition_identifier=partition.external_id,
    seismic_store_id=store.id,
    cutout = EmptyCutout(),
    metadata = {
        "optional": "metadata",
        "other": "metadata",
    },
)
pprint(seismic.extent)

[38]:
Seismic3dDef(major_header=TraceHeaderField.INLINE,
             minor_header=TraceHeaderField.CROSSLINE,
             lines={})

[39]:
from cognite.seismic import Seismic3dRect

# Using a rectangle of traces:
# Every 10th inline from 2000 to 2100, every 20th xline from 3000 to 3200 (inclusive)
cutout = Seismic3dRect(inline=(2000, 2100, 10), xline=(3000, 3200, 20))

seismic = client.seismic.create(
    external_id="seismic-from-rect",
    partition_identifier=partition.external_id,
    seismic_store_id=store.id,
    cutout = cutout,
    metadata = {
        "optional": "metadata",
        "other": "metadata",
    },
)
pprint(seismic.extent)

[39]:
Seismic3dDef(major_header=TraceHeaderField.INLINE,
             minor_header=TraceHeaderField.CROSSLINE,
             lines={2000: [RangeInclusive(4000, 4200, 20)],
                    2010: [RangeInclusive(4000, 4200, 20)],
                    2020: [RangeInclusive(4000, 4200, 20)],
                    2030: [RangeInclusive(4000, 4200, 20)],
                    2040: [RangeInclusive(4000, 4200, 20)],
                    2050: [RangeInclusive(4000, 4200, 20)],
                    2060: [RangeInclusive(4000, 4200, 20)],
                    2070: [RangeInclusive(4000, 4200, 20)],
                    2080: [RangeInclusive(4000, 4200, 20)],
                    2090: [RangeInclusive(4000, 4200, 20)],
                    3000: [RangeInclusive(4000, 4200, 20)]})

[40]:
from cognite.seismic import Seismic3dDef

# Or a more detailed mapping from inline to crossline
cutout = Seismic3dDef.inline_major({3000: [(3000, 3100, 10)], 3010: [(3010, 3110, 10)], 3020: [(3020, 3120, 10)]})

seismic = client.seismic.create(
    external_id="seismic-from-def",
    partition_identifier=partition.external_id,
    seismic_store_id=store.id,
    cutout = cutout,
    metadata = {
        "optional": "metadata",
        "other": "metadata",
    },
)
pprint(seismic.extent)

[40]:
Seismic3dDef(major_header=<TraceHeaderField.INLINE: 3>,
             minor_header=<TraceHeaderField.CROSSLINE: 4>,
             lines={3000: [RangeInclusive(3000, 3100, 10)],
                    3010: [RangeInclusive(3010, 3110, 10)],
                    3020: [RangeInclusive(3020, 3120, 10)]})

[41]:
# Via geometry
from cognite.seismic import Geometry, GeometryCutout
## Wkt
geometry = Geometry(wkt="POLYGON ((4.3 72.6, 4.35 72.6, 4.35 72.8, 4.3 72.8, 4.3 72.6))", crs="epsg:4326")

## GeoJSON
geojson = {
    "type": "Feature",
    "properties":{},
    "geometry": {
        "type": "Polygon",
        "coordinates": [[[4.3, 72.6],[4.35, 72.6],[4.35, 72.8],[4.3, 72.8],[4.3, 72.6]]]
    }
}

geometry = Geometry(geojson=geojson, crs="epsg:4326")

seismic = client.seismic.create(
    external_id="seismic-from-polygon",
    partition_identifier=partition.external_id,
    seismic_store_id=store.id,
    cutout=GeometryCutout(geometry),
)

[42]:
# Cutout a 2d seismic

cutout = Seismic2dExtent.cdp_range((5000, 8000, 5))
seismic = client.seismic.create(
    external_id="seismic-2d",
    partition_identifier=partition.external_id,
    seismic_store_id=store_2d.id,
    cutout = cutout,
    metadata = {
        "optional": "metadata",
        "other": "metadata",
    },
)
pprint(seismic.extent)
[42]:
Seismic2dExtent(trace_key=TraceHeaderField.CDP
                ranges=[RangeInclusive(5000, 8000, 5)])

Searching seismics

Similar to partitions, you can search seismics by id, external id, external id substring, name, or name substring. Unlike partitions, not all data is retrieved by default and must be specified.

[43]:
from cognite.seismic import SearchSpecSeismic
results = client.seismic.search(
    # The search spec allows you to search by id, name, external_id, or substrings of the latter two.
    # Pass a SearchSpecSurvey or SearchSpecSeismicStore to search for seismics belonging to a survey or
    # seismicstore instead.
    search_spec=SearchSpecSeismic(external_id_substring="seismic-from"),
    # The following are data inclusion options and are all optional and default to falsey values.
    include_text_header=True,
    include_binary_header=True,
    # Requests a precise description of the traces contained in the seismic
    include_extent=False,
    # Requests the cutout that the seismic was created with
    include_cutout=False,
    include_seismic_store=False,
    include_partition=False,
    # Can also retrieve coverage.
    coverage_crs="epsg:4326",
    coverage_format="wkt"
)
for s in itertools.islice(results, 2):
    pprint(s)
[43]:
Seismic(id=141216751086266,
        external_id='seismic-from-rect',
        crs='EPSG:23031',
        metadata={},
        trace_header_fields=[TraceHeaderField.INLINE,
                             TraceHeaderField.CROSSLINE],
        dimensions=3)
Seismic(id=206878240792925,
        external_id='seismic-from-def',
        crs='EPSG:23031',
        metadata={},
        trace_header_fields=[TraceHeaderField.INLINE,
                             TraceHeaderField.CROSSLINE],
        dimensions=3)

[44]:
# Alternatively, you can use seismics.get() to retrieve a specific seismic and,
# by default, all of its details except coverage.
seismic = client.seismic.get(
    external_id="testing-coverage"
)
pprint(seismic)
print()
# To get coverage, specify the required information.
seismic = client.seismic.get(
    external_id="testing-coverage",
    coverage_crs="epsg:4326",
    coverage_format="wkt"
)
print("Coverage:")
pprint(seismic.coverage)
[44]:
Seismic(id=7203554872996549,
        external_id='testing-coverage',
        crs='EPSG:23031',
        metadata={},
        trace_header_fields=[TraceHeaderField.INLINE,
                             TraceHeaderField.CROSSLINE],
        dimensions=3)

Coverage:
Geometry(crs='epsg:4326', wkt='POLYGON((-1.488209131116166 0.00018038567226972462,-1.4883883041593666 0.0003607711200884963,-1.4885674769896076 0.0001803855831062832,-1.4883883040708041 0,-1.488209131116166 0.00018038567226972462))')

[45]:
# You can access seismic data through its members:
seismic = client.seismic.get(id=7203554872996549)
print(
    seismic.id,
    seismic.external_id,
    # and so on. Refer to the API Types documentation
)
[45]:
7203554872996549 testing-coverage

Retrieving data from seismics

Seismics function almost identically to seismic stores for purposes of retrieving trace data. They use the same traces endpoint, only changing seismic_store_id to seismic_id.

[46]:
# Can retrieve inline/xline ranges similarly to seismic stores
pprint(client.traces.get_trace_bounds(seismic_id=seismic.id))
[46]:
TraceBounds3d(num_traces=25,
              sample_count=50,
              size_kilobytes=5,
              crs='EPSG:23031',
              z_range=RangeInclusive(0, 49, 1),
              inline_bounds=RangeInclusive(1, 5, 1),
              xline_bounds=RangeInclusive(20, 24, 1))

[47]:
# Retrieving volumes is nearly identical
extent=Seismic3dRect(inline=(1, 4), xline=(22, 24, 2))
trace_stream = client.traces.stream_traces(seismic_id=seismic.id, extent=extent)
array = client.traces.get_array(seismic_id=seismic.id, extent=extent)

# Or for 2D
extent = Seismic2dExtent.shotpoint_range((100, 200))
array = client.traces.get_array(seismic_id=seismic_2d.id, extent=extent)