Migration guide 0.3 SDK

This is a guide to help migrating from SDK version 0.2.X to 0.3. This guide assumes that the code to be migrated is using the v1 API of the 0.2.X SDK. Refer to the v0 to v1 migration guide if the code is using the v0 API.

Overview

All v0 APIs have been removed. As a consequence, some V1 APIs have been renamed.

SDK 0.2 0.3
Survey survey/survey_v1 survey
File file/file_seismic file
Volume volume_seismic trace
Seismic store seismicstore seismicstore
Seismic seismic seismic
Job job job
Partition partition partition

Search changes

The way search parameters are passed to search methods for seismic store, partitions, and seismics have been changed. Instead of setting the search mode, a SearchSpec object that describes the nature of the search is passed to the search methods. For example, a search(mode='seismicstore', name_substring='my-substring') call will instead be search(search_spec=SearchSpecSeismicStore(name_substring='my-substring')). Details can be found in the documentation for each search method, as well as in the endpoint-by-endpoint guide below.

Identifier changes

All methods that previously accepted a string-based id argument now accept it as uuid. All methods that require unique identifiers (particularly file and survey) now accept external_id and numeric id (excluding seismic stores, which do not have external ids). This is to standardize object identifiers with Cognite Data Fusion. While uuid will continue to be supported, migrating to id and external_id is highly recommended.

Seismic 2D support

Support for 2D seismic data has been added and is exclusively available via the 0.3 SDK. The SDK has been reworked to accomodate the changes required for 2D seismic, so 2D features are only available via 0.3.

VolumeDef retired

VolumeDef is now a legacy format and is replaced. Line ranges are replaced with extents, and trace counts are replaced with a new trace count endpoint, trace.get_trace_bounds(). This work is in support of 2D functionality.

Endpoint-by-endpoint guide

The following is a comprehensive endpoint-by-endpoint comparison between 0.2 and 0.3 methods.

Client setup

[2]:
# Setting up the client
from cognite.seismic import CogniteSeismicClient

base_url="https://greenfield.cognitedata.com"
project="seismic-test"
[8]:
# Key-based auth
import getpass

api_key = getpass.getpass("Enter your Cognite API key: ")

client = CogniteSeismicClient(
    api_key=api_key,
    base_url=base_url,
    project=project
)
[3]:
# OAuth
import msal

tenant_id = "cogniteseismic.onmicrosoft.com"
client_id = "CLIENT ID FOR OIDC"

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?")

    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)

Survey

Creating a survey now uses the “create” call. The main difference is that an external_id must be provided. Additionally, the crs argument is mandatory.

[3]:
# 0.2
# client.survey.register(survey_name)
# client.survey.get(
#     survey_name="my new survey",
#     # Can set these to True or False depending on requirements
#     list_seismics=True,
#     list_seismic_stores=True
# )

# 0.3
client.survey.create(name='my new survey', external_id='my-new-survey', crs='EPSG:4326')
survey = client.survey.get(
    external_id='my-new-survey'
)
survey
[3]:
Survey(id=8233797238426673, external_id='my-new-survey', name='my new survey', seismic_ids=[], metadata={})

Getting surveys is largely unchanged. Like in the 0.2 SDK, surveys can be fetched by name or string id. In the latter case, the id should be provided via the uuid parameter. Surveys can also be fetched by numeric id and external id, and this is the preferred method of identifying surveys.

[4]:
# 0.2
# client.survey_v1.get(survey_name=survey.name)
# client.survey_v1.get(survey_id=survey.uuid)

# 0.3
client.survey.get(name=survey.name)
client.survey.get(uuid=survey.uuid)  # Previously "id"
client.survey.get(external_id=survey.external_id)
client.survey.get(id=survey.id)  # Numerical internal id
[4]:
Survey(id=8697773075018874, external_id='my-new-survey', name='my new survey', seismic_ids=[], metadata={})

Searching for and editing surveys is also mostly unchanged, aside from the id/uuid difference as well as the addition of external_id and numeric id.

[6]:
# 0.2
# client.survey_v1.search(name_substring='survey')
#
# client.survey_v1.edit(id=survey.id, crs='EPSG:23031')

# 0.3
client.survey.search(name_substring='survey')
client.survey.search(uuid=survey.uuid)  # Was not possible to search by uuid in 0.2

client.survey.edit(id=survey.id, crs='EPSG:23031')  # uuid is not supported
[6]:
Survey(id=8233797238426673, external_id='my-new-survey', name='my new survey', seismic_ids=[], metadata={})

Surveys can be only be deleted using (numeric) id and external_id. If the id or external_id is not known, geting the survey and passing survey.id is recommended.

[ ]:
# 0.2
# client.survey_v1.delete(name=survey.name)

# 0.3
client.survey.delete(external_id=survey.external_id)  # uuid and name-based deletion is not supported.

Files

File endpoints are mostly unchanged. uuids are replaced by id and external_id is mandatory, and file_seismic is replaced by file.

[ ]:
# 0.2
# client.file_seismic.register(...)
# client.file_seismic.ingest(...)
# client.file_seismic.get(...)

client.file.register(...)
client.file.ingest(...)
client.file.get(...)

Like seismic stores, searches now utilize SearchSpec. Previously, files could only be searched for by file identifiers (id, name, substring). Now files can also be searched for by seismic store or survey identifiers.

[3]:
from cognite.seismic import SearchSpecSurvey, SearchSpecFile, SearchSpecSeismicStore

file_uuid = '99862bf1-9ae6-420e-a40a-5cff134344c3'
file_id = 521798174165669

# 0.2
# client.file.search(id=file_uuid)

# 0.3
client.file.search(search_spec=SearchSpecFile(id=file_id))  # by file identifiers
client.file.search(search_spec=SearchSpecSurvey(name='survey'))  # by survey identifiers
client.file.search(search_spec=SearchSpecSeismicStore(id=205818405704076))  # by seismic store identifiers
[3]:
<generator object FileAPI.search.<locals>.<genexpr> at 0x11e7be510>

Partitions

Partitions do not have any changes.

Seismic stores

Seismic store objects no longer have volume def fields. These are replaced with extents and get_trace_bounds:

[4]:
# An example seismic store
store_id = 205818405704076
store = client.seismicstore.get(id=store_id)

## Getting i/xline ranges
# 0.2
# store.inline_volume_def.get_inline_range()

# 0.3
# 3d extents are divided into three types:
# - Seismic3dRect: A simple rectangular extent
# - Seismic3dRects: Multiple rectangles
# - Seismic3dDef: A mapping from inline or crossline to, respectively, crossline or inline.

# Simply retrieving bounds is easiest done with the get_trace_bounds endpoint:
bounds = client.traces.get_trace_bounds(seismic_store_id=store_id)
print(f'Bounds: inline {bounds.inline_bounds} crossline {bounds.xline_bounds}\n')

# More detailed analysis can be run directly on extents, which replace VolumeDefs.
# Docs have details on Seismic3dRect/Seismic3dRects/Seismic3dDef
from pprint import pprint

pprint(store.extent)
print()

## Getting trace counts
# 0.2
# store.inline_volume_def.count_total_traces()

# 0.3 using the get_trace_bounds endpoint
print(f'Traces: {client.traces.get_trace_bounds(seismic_store_id=store_id).num_traces}')
Bounds: inline RangeInclusive(1, 5, 1) crossline RangeInclusive(20, 24, 1)

Seismic3dDef(major_header=<TraceHeaderField.INLINE: 3>,
             minor_header=<TraceHeaderField.CROSSLINE: 4>,
             lines={1: [RangeInclusive(20, 24, 1)],
                    2: [RangeInclusive(20, 24, 1)],
                    3: [RangeInclusive(20, 24, 1)],
                    4: [RangeInclusive(20, 24, 1)],
                    5: [RangeInclusive(20, 24, 1)]})

Traces: 25

Similar to partitions, seismic store search now utilizes SearchSpec objects rather than a mode switch.

[11]:
# 0.2
# client.seismicstore.search(mode='survey', name_substring='sgy')
# client.seismicstore.search(get_all=True)

# 0.3
from cognite.seismic import SearchSpecGetAll, SearchSpecSurvey
client.seismicstore.search(search_spec=SearchSpecSurvey(name_substring='sgy'))
client.seismicstore.search(search_spec=SearchSpecGetAll())  # get_all is removed in favor of a SearchSpec.

[11]:
<generator object SeismicStoreAPI.search at 0x11df092e0>

Seismics

Seismics, like seismic stores, use extents instead of VolumeDef for describing traces.

[12]:
# 0.2
# An example seismic
seismic_id = 141216751086266

## Getting i/xline ranges
# (Refer to the section on seismic stores)

## Getting trace counts
# 0.2
# store.inline_volume_def.count_total_traces()

# 0.3 using the get_trace_bounds endpoint
print(f'Traces: {client.traces.get_trace_bounds(seismic_id=seismic_id).num_traces}')
Traces: 25

Seismic search uses SearchSpecs instead of a mode switch. Like seismic stores, getting all seismics uses SearchSpecGetAll instead of a get_all parameter.

[13]:
# 0.2
# client.seismic.search(mode='partition', external_id_substring='my-partition')
# client.seismic.search(get_all=True)

# 0.3
from cognite.seismic import SearchSpecPartition, SearchSpecGetAll
client.seismic.search(search_spec=SearchSpecPartition(external_id_substring='my-partition'))
client.seismic.search(search_spec=SearchSpecGetAll())
[13]:
<generator object SeismicAPI.search at 0x11deee7b0>

Trace data

Trace data is now handled through the trace API and is also available on SeismicStore and Seismic objects. Inline/xline access should use Seismic3dRect. It is now also possible to stream multiple rects at once with Seismic3dRects, and specific traces can be streamed with Seismic3dDef, similarly to previous VolumeDef usage.

[4]:
from cognite.seismic import Geometry, Seismic3dRect

crs = 'EPSG:4326'
wkt = 'POLYGON((-1.488209131092365 0,-1.488209131180926 0.0003607712069160569,-1.488567477056032 0.0003607710285892414,-1.4885674769674655 0,-1.488209131092365 0))'
geom = Geometry(crs=crs, wkt=wkt)
seismic_id = 141216751086266

## Getting volumes
# 0.2
# client.volume_seismic.get_volume(id=seismic_id, inline_range=(20, 24), xline_range=(20, 24))  # by line range
# client.volume_seismic.get_volume(id=seismic_id, geometry=geom)  # by geometry

# 0.3
client.traces.stream_traces(seismic_id=seismic_id, extent=Seismic3dRect(inline=(20, 24), xline=(20, 24)))  # by line range
client.traces.stream_traces(seismic_id=seismic_id, geometry=geom)  # by geometry
[4]:
<generator object TracesAPI.stream_traces at 0x11e7be890>
[15]:
# An example seismic store
store_id = 205818405704076
store = client.seismicstore.get(id=store_id)

## Streaming Seg-Y files
# 0.2
# client.file_seismic.get_segy(seismic_store_id=store_id)

# 0.3
client.traces.get_segy(store_id=store_id)
store.get_segy()  # Can also use SeismicStore objects directly to stream SegY files
[15]:
<generator object TracesAPI.get_segy at 0x11df36350>