R2camtrapdp: 音声(音響)データ(日本語)

library(R2camtrapdp)

概要

音声(音響)録音は Camtrap DP の bioacoustics フレーバーで扱います。音声データは メディアベースobservationLevel = "media")で、各観察はメディアファイル (mediaID)を参照します。カメラトラップとの差分は音響スキーマから自動的に読み取られ、 主な点は次のとおりです。

ヒント — 日時は POSIXct で渡す。 そうすれば各テーブルの書式へ正しく整形され (オフセット +0900、必要な箇所には小数秒 .000)。"2026/6/12 12:00:00" の ような生文字列はそのまま書き出され検証に失敗します。

この例では、野帳が 設置情報の野帳観察情報の野帳の2つだけあり、観察野帳に 音声のファイル名が記録されている前提で、そこから media を作成します。

データ

data("Adep")   # 設置情報の野帳(デバイス設置1件=1行)
data("Aobs")   # 観察情報の野帳(観察1件=1行。`filename` を持つ)
str(Adep, vec.len = 2)
#> 'data.frame':    2 obs. of  14 variables:
#>  $ deploymentID     : chr  "AC01" "AC02"
#>  $ longitude        : num  139 139
#>  $ latitude         : num  34.9 34.9
#>  $ locationID       : chr  "L01" "L02"
#>  $ startDate        : chr  "2026-06-12" "2026-06-13"
#>  $ startTime        : chr  "06:00:00" "06:00:00"
#>  $ endDate          : chr  "2026-06-20" "2026-06-21"
#>  $ endTime          : chr  "06:00:00" "06:00:00"
#>  $ deviceID         : chr  "AM1" "AM2"
#>  $ deviceModel      : chr  "AudioMoth 1.2.0" "AudioMoth 1.2.0"
#>  $ samplingFrequency: int  48000 48000
#>  $ bitDepth         : int  16 16
#>  $ channels         : int  1 1
#>  $ setupBy          : chr  "Jane Doe" "Jane Doe"
str(Aobs, vec.len = 2)
#> 'data.frame':    6 obs. of  19 variables:
#>  $ institutionCode: chr  "NIES" "NIES" ...
#>  $ collectionCode : chr  "ACO" "ACO" ...
#>  $ obsID          : chr  "1" "2" ...
#>  $ eventID        : chr  "e1" "e1" ...
#>  $ deploymentID   : chr  "AC01" "AC01" ...
#>  $ locationID     : chr  "L01" "L01" ...
#>  $ date           : chr  "2026-06-12" "2026-06-12" ...
#>  $ time           : chr  "06:00:00" "06:00:00" ...
#>  $ filename       : chr  "AM1_20260612_060000.wav" "AM1_20260612_060000.wav" ...
#>  $ duration       : num  60 60 60 60 60 ...
#>  $ object         : chr  "animal" "animal" ...
#>  $ class          : chr  "Aves" "Aves" ...
#>  $ genus          : chr  "Strix" "Cuculus" ...
#>  $ species        : chr  "uralensis" "canorus" ...
#>  $ individualCount: int  NA NA NA NA NA ...
#>  $ frequencyLow   : num  300 500 300 NA 500 ...
#>  $ frequencyHigh  : num  900 900 900 NA 900 ...
#>  $ eventStart     : chr  "2026-06-12 06:00:05" "2026-06-12 06:00:12" ...
#>  $ eventEnd       : chr  "2026-06-12 06:00:09" "2026-06-12 06:00:18" ...

AdepdeploymentID、座標、startDate/startTimeendDate/endTimedeviceIDdeviceModel、録音設定(samplingFrequency, bitDepth, channels)、setupBy を持ちます。AobsdeploymentIDfilenamedate/timeduration、分類(class/genus/species)、individualCountfrequencyLow/frequencyHigheventStart/eventEnd を持ちます。この例データでは、 座標は伊豆半島内、individualCountNA(音響では個体数を数えない)、 frequencyLow/frequencyHigh は文献に基づく概算値です。

1. bioacoustics フレーバーを指定

ba <- "https://raw.githubusercontent.com/camera-traps/bioacoustics/main/camtrap-dp/1.0.2/%s"

dp <- R6_CamtrapDP$new(version = "1.0.2",
  title = "音響調査の例", description = "AudioMoth 録音",
  id = "https://example.org/dataset/acoustic-1")

dp$set_properties(
  version     = "1.0.2",
  profile     = sprintf(ba, "camtrap-dp-profile-acoustic.json"),
  schema_urls = list(
    deployments  = sprintf(ba, "deployments-table-schema-acoustic.json"),
    media        = sprintf(ba, "media-table-schema-acoustic.json"),
    observations = sprintf(ba, "observations-table-schema-acoustic.json")))

2. deployments(設置野帳から)

Adep から deployments を作成します。日付列と時刻列を POSIXct に結合して渡すと、 正しい日時書式で書き出されます。

deployments <- data.frame(
  deploymentID    = Adep$deploymentID,
  latitude        = Adep$latitude,
  longitude       = Adep$longitude,
  locationID      = Adep$locationID,
  deploymentStart = as.POSIXct(paste(Adep$startDate, Adep$startTime), tz = "Asia/Tokyo"),
  deploymentEnd   = as.POSIXct(paste(Adep$endDate,   Adep$endTime),   tz = "Asia/Tokyo"),
  deviceID        = Adep$deviceID,
  deviceModel     = Adep$deviceModel,
  setupBy         = Adep$setupBy,
  stringsAsFactors = FALSE)
dp$set_deployments(deployments)

3. media(観察野帳のファイル名から生成)

メディア用の野帳は無いので、Aobs のユニークな filename(音声ファイル1件=1行)から media を作り、録音設定は Adep から結合します。

files <- Aobs[!duplicated(Aobs$filename), ]   # 音声ファイルごとに1行

media <- data.frame(
  mediaID       = files$filename,             # ファイル名をメディア識別子に
  deploymentID  = files$deploymentID,
  timestamp     = as.POSIXct(paste(files$date, files$time), tz = "Asia/Tokyo"),
  filePath      = file.path("audio", files$filename),
  filePublic    = TRUE,
  fileMediatype = paste0("audio/", tolower(tools::file_ext(files$filename))),  # "audio/wav"
  duration      = files$duration,
  stringsAsFactors = FALSE)

# 録音設定(samplingFrequency / bitDepth / channels)を Adep から付与
media <- merge(media, Adep[, c("deploymentID", "samplingFrequency", "bitDepth", "channels")],
               by = "deploymentID", all.x = TRUE)
head(media)
#>   deploymentID                 mediaID           timestamp
#> 1         AC01 AM1_20260612_060000.wav 2026-06-12 06:00:00
#> 2         AC01 AM1_20260612_063000.wav 2026-06-12 06:30:00
#> 3         AC01 AM1_20260613_060000.wav 2026-06-13 06:00:00
#> 4         AC02 AM2_20260613_060000.wav 2026-06-13 06:00:00
#>                        filePath filePublic fileMediatype duration
#> 1 audio/AM1_20260612_060000.wav       TRUE     audio/wav       60
#> 2 audio/AM1_20260612_063000.wav       TRUE     audio/wav       60
#> 3 audio/AM1_20260613_060000.wav       TRUE     audio/wav       60
#> 4 audio/AM2_20260613_060000.wav       TRUE     audio/wav       60
#>   samplingFrequency bitDepth channels
#> 1             48000       16        1
#> 2             48000       16        1
#> 3             48000       16        1
#> 4             48000       16        1
dp$set_media(media)

timestampPOSIXct なので、音響 media の書式に合わせて小数秒付き (例 2026-06-12T06:00:00.000+0900)で自動的に書き出されます。

4. observations(観察野帳から)

observations <- data.frame(
  observationID    = paste(Aobs$deploymentID, Aobs$eventID, Aobs$obsID, sep = "_"),
  deploymentID     = Aobs$deploymentID,
  mediaID          = Aobs$filename,           # media へのリンク(mediaID = filename)
  eventStart       = as.POSIXct(Aobs$eventStart, tz = "Asia/Tokyo"),
  eventEnd         = as.POSIXct(Aobs$eventEnd,   tz = "Asia/Tokyo"),
  observationLevel = "media",
  observationType  = ifelse(Aobs$object == "none", "blank",
                     ifelse(Aobs$object == "hito", "human", "animal")),
  scientificName   = ifelse(is.na(Aobs$genus), Aobs$class, paste(Aobs$genus, Aobs$species)),
  count            = Aobs$individualCount,    # 音響では個体数を数えないため NA
  frequencyLow     = Aobs$frequencyLow,
  frequencyHigh    = Aobs$frequencyHigh,
  stringsAsFactors = FALSE)
dp$set_observations(observations)

5. メタデータ・リレーション・書き出し・検証

dp$add_contributors(data.frame(title = "Jane Doe", role = "contact",
                               organization = "NIES", stringsAsFactors = FALSE))
dp$add_license(name = "CC0-1.0",   scope = "data")
dp$add_license(name = "CC-BY-4.0", scope = "media")
dp$set_project(title = "音響調査", samplingDesign = "systematicRandom",
               captureMethod = "recordingSchedule", individualAnimals = FALSE,
               observationLevel = "media")
dp$set_st()
# dp$set_taxon()   # GBIF/ITIS/NCBI から taxonID。taxadb パッケージ+ネット接続が必要

dp$check_relations()   # PK/FK。キー列が欠損なら datapackage$data$... を指す警告を出す

path <- file.path(tempdir(), "acoustic-package")
dp$out_camtrapdp(write = TRUE, directory = path)

issues <- dp$validate_frictionless(directory = path, python = "python")  # 要 pip install frictionless
ctdp_is_valid(issues)

既にディスク上にあるパッケージを上書きせず検証だけする場合:

ctdp_validate_frictionless("path/to/existing/acoustic-package", python = "python")

6. 音響スキーマの要件確認

ba <- "https://raw.githubusercontent.com/camera-traps/bioacoustics/main/camtrap-dp/1.0.2/%s"
acoustic_media <- TableSchema$new(
  "media", version = "1.0.2",
  url_template = sprintf(ba, "media-table-schema-acoustic.json"))

acoustic_media$field_names()
acoustic_media$requirements()                 # 各列の type / format / required / enum
acoustic_media$field("timestamp")$format      # "%Y-%m-%dT%H:%M:%S.%f%z"