2026年6月26日金曜日

Blender3D空間に分布する点群(メッシュ)のCT撮影用Pythonスクリプト

 Python Script for CT-Style Imaging of Point Clouds (Meshes) in Blender 3D Space


I have created a Python script to perform CT-style imaging on point clouds (meshes) distributed within Blender's 3D space. This allows for the generation of data analogous to medical CT scans of internal organs, but applied instead to the spatial distribution of artifacts within a shell midden. This marks the creation of a prototype for a key tool in 3D spatial analysis of artifacts. Moving forward, I plan to utilize this tool in spatial analysis while refining its usability.


Blender3D空間に分布する点群(メッシュ)のCT撮影用Pythonスクリプトを作成しました。体内臓器のCT撮影と同じようなデータを貝塚貝層内遺物分布にあてはめて生成できます。遺物3D空間分析の重要ツールの一つの祖型ができたことになります。これからこのツールを3D空間分析で活用して、その使い勝手を改善していくことにします。

1 点群(メッシュ)のCT撮影用Pythonスクリプトについて

このスクリプトは、Blender3Dビューポートに分布する点群(メッシュ)について、Y軸方向に多数のXZ断面を設定し、各XZ断面に近傍の点群を投影した投影断面図を作成するBlenderPythonスクリプトです。


点群(メッシュ)のCT撮影

断面図の間隔は0.1mとか0.5mとか1mとか自由に設定できます。また画面や点の色、点の大きさも自由に設定できます。


CT撮影結果(点群投影断面図)1枚目ct_slice_0000.png


CT撮影結果(点群投影断面図)2枚目ct_slice_0001.png


CT撮影結果(点群投影断面図)3枚目ct_slice_0002.png

2 使い方

Blender3Dビューポートで選択された点群(メッシュ)についてCT撮影を行います。点群をgeometry nodesで操作してテーマに沿って絞り込んで表現されている場合でも、その絞り込んだ点群を選択すれば、それを対象にCT撮影します。

下のBlenderPythonスクリプトの設定欄を好みに設定して、点群が配置されているBlenderファイルのテクストエディターに貼りつけ、走らせます。

Blenderファイルが存在するフォルダーにct_slices名称のサブフォルダーがつくられ、点群投影断面図がその中に生成します。生成スピートは高速です。(自分のパソコンでは84枚で10秒ほどです。)

3 点群(メッシュ)のCT撮影用Pythonスクリプト


import bpy
import os
import math

# ==========================
# ユーザー設定
# ==========================
# スライス厚み (m) : Y 方向の幅
SLICE_THICKNESS = 0.1

# 出力画像の「基準横幅」(px)
# 高さはバウンディングボックスの XZ 比率を保つように自動計算されます
IMAGE_WIDTH = 1024

# 点の色 (R, G, B, A) 0.0〜1.0
POINT_COLOR = (1.0, 1.0, 0.0, 1.0)  # kiiro
# 背景色 (R, G, B, A)
BACKGROUND_COLOR = (0.027, 0.051, 0.498, 1.0)  # ao

# 何ピクセル四方に塗るか(点の大きさ)
POINT_SIZE = 2  # 1なら1ピクセルのみ,2なら3x3ピクセル,3なら5x5ピクセル相当

# 出力先ディレクトリ(.blendファイルからの相対パス)
OUTPUT_DIR = "//ct_slices"

# 対象オブジェクトを「選択中」に限定するか
# True: 3Dビューポートで選択中のメッシュだけを対象にします
# False: シーン内の表示中メッシュを対象にします
USE_SELECTED_OBJECTS_ONLY = True

# Geometry Nodes / モディファイア適用後の「ビューポート表示結果」から頂点を取るか
# Geometry Nodesで点群を絞って表示している場合は True のまま使ってください
USE_EVALUATED_VIEWPORT_MESH = True

# 非表示オブジェクトを除外するか
EXCLUDE_HIDDEN_OBJECTS = True
# ==========================



def collect_world_vertices():
    """選択中(または表示中)メッシュの頂点をワールド座標で取得。

    USE_EVALUATED_VIEWPORT_MESH=True の場合は、Geometry Nodesやモディファイア適用後の
    ビューポート評価結果から頂点を取得する。
    """
    view_layer = bpy.context.view_layer
    depsgraph = bpy.context.evaluated_depsgraph_get()

    if USE_SELECTED_OBJECTS_ONLY:
        source_objects = list(bpy.context.selected_objects)
    else:
        source_objects = list(bpy.context.scene.objects)

    objects = []
    for obj in source_objects:
        if obj.type != 'MESH':
            continue
        if EXCLUDE_HIDDEN_OBJECTS and (obj.hide_get(view_layer=view_layer) or obj.hide_viewport):
            continue
        objects.append(obj)

    print("対象オブジェクト:", ", ".join(obj.name for obj in objects) if objects else "なし")

    points = []
    for obj in objects:
        mat = obj.matrix_world

        if USE_EVALUATED_VIEWPORT_MESH:
            obj_eval = obj.evaluated_get(depsgraph)
            mesh = None
            try:
                mesh = obj_eval.to_mesh(preserve_all_data_layers=False, depsgraph=depsgraph)
                for v in mesh.vertices:
                    co_world = mat @ v.co
                    points.append((co_world.x, co_world.y, co_world.z))
            finally:
                if mesh is not None:
                    obj_eval.to_mesh_clear()
        else:
            mesh = obj.data
            for v in mesh.vertices:
                co_world = mat @ v.co
                points.append((co_world.x, co_world.y, co_world.z))

    return points


def compute_bounds(points):
    """頂点群の XYZ の最小値・最大値を返す"""
    xs = [p[0] for p in points]
    ys = [p[1] for p in points]
    zs = [p[2] for p in points]

    min_x, max_x = min(xs), max(xs)
    min_y, max_y = min(ys), max(ys)
    min_z, max_z = min(zs), max(zs)

    return (min_x, max_x, min_y, max_y, min_z, max_z)


def bucket_points_by_slice(points, min_y, max_y, thickness):
    """Y方向のスライスごとに頂点をバケツ分けしたリストを返す"""
    if max_y - min_y == 0.0:
        num_slices = 1
    else:
        num_slices = int(math.floor((max_y - min_y) / thickness)) + 1

    slices = [[] for _ in range(num_slices)]

    for (x, y, z) in points:
        idx = int((y - min_y) / thickness)
        # 安全のためクランプ
        idx = max(0, min(num_slices - 1, idx))
        slices[idx].append((x, y, z))

    return slices, num_slices


def create_blank_image(name, width, height, bg_color):
    """背景色で塗られた空画像を作成して返す"""
    img = bpy.data.images.new(name=name, width=width, height=height, alpha=True, float_buffer=True)

    # すべて背景色で初期化
    r, g, b, a = bg_color
    num_pixels = width * height
    pixels = [0.0] * (num_pixels * 4)
    for i in range(num_pixels):
        base = i * 4
        pixels[base]     = r
        pixels[base + 1] = g
        pixels[base + 2] = b
        pixels[base + 3] = a

    img.pixels = pixels
    return img


def draw_points_to_image(img, points_3d, bounds, point_color, point_size):
    """XZ平面に投影した点群を画像に描画する"""
    min_x, max_x, _, _, min_z, max_z = bounds
    width = img.size[0]
    height = img.size[1]

    # 既存のピクセルデータを「リスト」としてコピー(ここが重要)
    px = list(img.pixels)

    # XZ範囲がゼロにならないように
    range_x = max_x - min_x
    range_z = max_z - min_z
    if range_x == 0.0:
        range_x = 1.0
    if range_z == 0.0:
        range_z = 1.0

    scale_x = (width - 1) / range_x
    scale_z = (height - 1) / range_z

    pr, pg, pb, pa = point_color

    # point_size=1 のときは1ピクセルのみ、
    # 2以上のときは +-point_size ピクセルに塗る
    radius = max(0, point_size - 1)

    for (x, y, z) in points_3d:
        u = int((x - min_x) * scale_x)
        v = int((z - min_z) * scale_z)

        # 画像内に収まるチェック
        if u < 0 or u >= width or v < 0 or v >= height:
            continue

        for du in range(-radius, radius + 1):
            for dv in range(-radius, radius + 1):
                uu = u + du
                vv = v + dv
                if 0 <= uu < width and 0 <= vv < height:
                    idx = (vv * width + uu) * 4
                    px[idx]     = pr
                    px[idx + 1] = pg
                    px[idx + 2] = pb
                    px[idx + 3] = pa

    # 修正したリストを pixels に戻す
    img.pixels = px


def main():
    # 頂点収集
    points = collect_world_vertices()
    if not points:
        print("メッシュ頂点が見つかりません。対象オブジェクトを確認してください。")
        return

    # 全体バウンディングボックス
    bounds = compute_bounds(points)
    min_x, max_x, min_y, max_y, min_z, max_z = bounds
    print("Bounds X:[{}, {}], Y:[{}, {}], Z:[{}, {}]".format(min_x, max_x, min_y, max_y, min_z, max_z))

    # XZ方向の実長
    range_x = max_x - min_x
    range_z = max_z - min_z

    # 万が一ゼロにならないように
    if range_x == 0.0:
        range_x = 1.0
    if range_z == 0.0:
        range_z = 1.0

    # 横幅を基準にしてピクセルスケールを決定 (px / m)
    pixels_per_unit = IMAGE_WIDTH / range_x

    # XZ の比率をそのままに縦高さを決める
    img_width = IMAGE_WIDTH
    img_height = max(1, int(round(range_z * pixels_per_unit)))

    print(f"Image size (W x H): {img_width} x {img_height} (px)")

    # スライスごとに頂点を振り分け
    slices, num_slices = bucket_points_by_slice(points, min_y, max_y, SLICE_THICKNESS)
    print("スライス数 :", num_slices)

    # 出力ディレクトリ準備
    out_dir = bpy.path.abspath(OUTPUT_DIR)
    os.makedirs(out_dir, exist_ok=True)
    print("出力先ディレクトリ:", out_dir)

    # スライスごとに画像生成
    for i in range(num_slices):
        slice_points = slices[i]

        # スライス名
        img_name = f"ct_slice_{i:04d}"
        print(f"生成中: {img_name} (頂点数: {len(slice_points)})")

        # 空画像作成(ここで計算した img_width, img_height を使用)
        img = create_blank_image(img_name, img_width, img_height, BACKGROUND_COLOR)

        # 頂点を描画(XZ 投影)
        if slice_points:
            draw_points_to_image(img, slice_points, bounds, POINT_COLOR, POINT_SIZE)

        # PNGで保存
        filepath = os.path.join(out_dir, img_name + ".png")
        img.filepath_raw = filepath
        img.file_format = 'PNG'
        img.save()

    print("完了しました。")


if __name__ == "__main__":
    main()

有吉北貝塚北斜面貝層の骨歯3D分布CT撮影動画

 Video of 3D Bone and Tooth Distribution in the Northern Slope Shell Midden at Ariyoshi-kita Shell Midden (CT Scan)


I created a video visualizing the 3D distribution of bones and teeth (34,050 items) within the shell midden on the northern slope of the Ariyoshi-kita Shell Midden site by generating projected cross-sectional views at 0.5-meter intervals. Using a Python script, I processed CT scan data (69 frames) to produce a video with a frame interval of 0.2 seconds. I plan to compare these results with CT scan data of pottery and stone tools in the future.


有吉北貝塚北斜面貝層の骨歯3D分布(34050件)について幅0.5mスリット毎に投影断面図を作成し、動画としました。PythonスクリプトでCT撮影(コンピュータ断層撮影、69コマ)し、0.2秒間隔の動画にしたものです。今後土器や石器のCT撮影結果と比較する予定です。

この記事は2026.06.26記事「有吉北貝塚北斜面貝層の石器3D分布CT撮影動画」の続きです。

1 有吉北貝塚北斜面貝層の骨歯3D分布(34050件)


有吉北貝塚北斜面貝層の骨歯3D分布(34050件)

バウンティングボックスの大きさ:13.7m×11.7m×34.0m

2 有吉北貝塚北斜面貝層の骨歯3D分布CT撮影動画


有吉北貝塚北斜面貝層の骨歯3D分布CT撮影動画

画像の大きさ13.7m×11.7m

延長34.0m区間を0.5m幅スリット毎に土器分布投影図を作成(69コマ)し、0.2秒間隔で表示する動画。

骨歯は34050件。

3 メモ

土器、石器、骨歯のそれぞれの3D分布CT撮影画像比較は今後行う予定です。


有吉北貝塚北斜面貝層の石器3D分布CT撮影動画

Video of 3D Stone Tool Distribution in the Northern Slope Shell Midden at Ariyoshi-kita Shell Midden (CT-style Visualization)


I created a video visualizing the 3D distribution of stone tools (2,045 items) found in the northern slope shell midden at Ariyoshi-kita Shell Midden by generating projected cross-sections at 0.5-meter intervals. Using a Python script, I simulated a CT scan (computed tomography; 80 frames) to produce a video with 0.2-second intervals between frames. I plan to compare these results with CT-style visualizations of pottery, bones, and teeth in the future.


 有吉北貝塚北斜面貝層の石器3D分布(2045件)について幅0.5mスリット毎に投影断面図を作成し、動画としました。PythonスクリプトでCT撮影(コンピュータ断層撮影、80コマ)し、0.2秒間隔の動画にしたものです。今後土器や骨歯のCT撮影結果と比較する予定です。

この記事は2026.06.25記事「有吉北貝塚北斜面貝層の土器3D分布CT撮影動画」の続きです。

1 有吉北貝塚北斜面貝層の石器3D分布(2045件)


有吉北貝塚北斜面貝層の石器3D分布(2045件)

バウンティングボックスの大きさ:13.2m×21.6m×41.9m

2 有吉北貝塚北斜面貝層の石器3D分布CT撮影動画


有吉北貝塚北斜面貝層の石器3D分布CT撮影動画

画像の大きさ13.2m×21.6m

延長41.9m区間を0.5m幅スリット毎に土器分布投影図を作成(80コマ)し、0.2秒間隔で表示する動画。

石器は20145件。

3 メモ

土器、石器、骨歯のそれぞれの3D分布CT撮影画像比較は今後行いますが、土器と石器は似ています。


2026年6月25日木曜日

有吉北貝塚北斜面貝層の土器3D分布CT撮影動画

 Video of CT-style visualization showing pottery distribution in the shell midden on the north slope of the Ariyoshi-kita Shell Midden


I created a video showing the 3D distribution of pottery (17,484 items) found in the shell midden on the north slope of the Ariyoshi-kita Shell Midden by generating projected cross-sections at 0.5-meter slit intervals. Using a Python script, I simulated a CT scan (computed tomography) consisting of 85 frames to produce a video with 0.2-second intervals between frames. I intend to utilize this CT-style imaging technique as a powerful tool for analyzing the 3D distribution of artifacts in the future.


有吉北貝塚北斜面貝層の土器3D分布(17484件)について幅0.5mスリット毎に投影断面図を作成し、動画としました。PythonスクリプトでCT撮影(コンピュータ断層撮影、85コマ)し、0.2秒間隔の動画にしたものです。今後遺物の3D分布分析の有力なツールとしてCT撮影を活用します。

1 有吉北貝塚北斜面貝層の土器3D分布(17484件)


有吉北貝塚北斜面貝層の土器3D分布(17484件)

バウンティングボックスの大きさ:12.6m×22.4m×42.5m

2 有吉北貝塚北斜面貝層の土器分布CT撮影動画


有吉北貝塚北斜面貝層の土器分布CT撮影動画

画像の大きさ12.6m×22.4m

延長42.5m区間を0.5m幅スリット毎に土器分布投影図を作成(85コマ)し、0.2秒間隔で表示する動画。

土器は17484件。

3 メモ

PythonスクリプトでCT撮影(コンピュータ断層撮影、85コマ)し、0.2秒間隔の動画にしたものです。今後遺物の3D分布分析の有力なツールとしてCT撮影を活用します。


2026年6月24日水曜日

有吉北貝塚北斜面貝層のデータ総集 その2

 Comprehensive Data Collection for the Shell Midden on the Northern Slope of the Ariyoshi-Kita Shell Mound: Part 2


I examined the 3D data of the shell midden on the northern slope of the Ariyoshi-Kita Shell Mound from an overall perspective, stepping back from the fine details. I noted observations based on the differences between the 3D distributions of pottery and those of bones and teeth. I hypothesize that during the final stage of shell midden formation (the new phase of the Kasori E-II style), a large-scale collapse and a high-concentration flow occurred in the western tributary at the head of the gully, and that the resulting secondary deposits extended all the way to the downstream end.


有吉北貝塚北斜面貝層の3Dデータを、詳細から離れて、全体として眺めてみました。土器3D分布と骨歯3D分布との違いから気が付いたことをメモしました。貝層形成最終段階(加曽利EⅡ式新段階期)にガリー谷頭西支谷で大規模な崩壊、高濃度流発生があり、その二次堆積物が最下流まで及んでいると思考しています。

この記事は2026.05.26記事「有吉北貝塚北斜面貝層データの総集」の続きです。

1 遺物データ3D分布

1-1 土器3D分布


土器3D分布

土器の件数は17484です。

画像には第2断面(左)と第3断面(右)を入れたあります。(以下同様)

1-2 石器3D分布


石器3D分布

石器の件数は2045です。

石器3D分布土器3D分布と似ています。

1-3 骨歯3D分布


骨歯3D分布

骨歯の件数は34050です。

骨歯の3D分布は貝層3D分布と近似すると考えることができます。

2 土器、石器、骨歯の相互関係

2-1 土器、石器の3D分布相互関係


土器、石器の3D分布相互関係

土器3D分布画像と石器3D分布画像をPhotoshopで「ピンライト」の関係でオーバーレイした画像です。

2-2 土器、骨歯の3D分布相互関係


土器、骨歯の3D分布相互関係

土器3D分布画像と骨歯3D分布画像をPhotoshopで「ピンライト」の関係でオーバーレイした画像です。

土器3D分布はあるが骨歯3D分布がない場所、逆に骨歯3D分布はあるが土器3D分布が虚弱な場所を見つけることができます。

2-3 石器、骨歯の3D分布相互関係


石器、骨歯の3D分布相互関係

石器3D分布画像と骨歯3D分布画像をPhotoshopで「ピンライト」の関係でオーバーレイした画像です。

3 予備思考 骨歯分布(≒貝殻分布)との関係から見た土器分布区分


予備思考 骨歯分布(≒貝殻分布)との関係から見た土器分布区分

土器、骨歯の3D分布相互関係図に予備思考結果をメモしました。骨歯3D分布を貝殻3D分布に見立てて、それと土器3D分布との関係を分析し、土器分布をa~kの11に区分しました。以下思考結果をメモします。

3-1 予備思考1

a:aの土器片分布は、点線で描いたような「台地縁から投棄された結果そのもの」では「ない」と思考します。aと台地縁の間に存在した「投棄結果貝層」が規模の大きな高濃度流で下流に一気に移動したと考え、その移動プロセス結果がaに残っていると考えます。高濃度流堆積物としての土器堆積です。

このように思考する根拠の一つは第2断面層序最下部付近から北斜面貝層最新土器型式(加曽利EⅡ式新段階土器)が出土していて、大規模な高濃度流を想定せざるを得ないことです。

b:bの土器分布は台地縁からの投棄結果であると考えます。第1断面谷頭急崖直下から加曽利EⅡ式中~新段階土器が出土しているので、その頃以降に投棄された土器がメインであると考えます。

c:cの土器分布はaから継続し、fへと連なる高濃度流堆積物分布が軸となっていると考えます。

d:dの土器分布は台地縁から投棄された土器の分布を示すと考えます。

e:土器密度がdで粗でeで密である理由の一つは斜面に投棄された土器が高濃度流で斜面下部に集中しやすいことが影響していると考えます。

f:fの土器分布はcの土器分布と連続するものですが、高濃度流の特性として粒子の大きいもの(土器>貝殻)が堆積先端に集中するので、f域は貝殻が伴っていない、つまり、高濃度堆積先端域と考えます。

g:fの土器がその後の高濃度流で二次堆積したと考えます。

h:hの方がdより土器が密集しているのは、土器投棄量がhの方がdより多かったと考えます。

i:dとeの関係のように、hよりiの方が土器は密集しています。

j:j域に台地縁から土器が投棄されたと考えます。

k:i域からk域に土器が移動していますが、その営力は高濃度流ではなく、流水による可能性があると考えます。この付近の貝層に水平ラミナが存在するからです。

3-2 予備思考2

a-c-f、a-c-e-i-k:この区域は、加曽利EⅡ式新段階期における左岸側(西側)支谷谷頭部の大規模崩壊による高濃度流堆積物をメインとすると考えます。その堆積物より下位にそれより古い時期の堆積物が断片的に存在する可能性はあると考えます。

b:この付近のガリー谷頭浸食は少なくとも阿玉台式期にはあったことが判っています。。加曽利EⅡ式中~新段階期に顕著なガリー谷頭浸食があり、その直後から土器、石器、貝殻の投棄が始まったことが判っています。

d,h:ガリー侵食後の崩落層形成時から貝殻投棄が始まっています。ガリー侵食時期は少なくとも中峠式期には遡ります。それより古い時期まで遡るかどうかは情報がないので現状では判断できません。中峠式期以降加曽利EⅡ式新段階期まで貝殻や土器の投棄は継続すると考えられますが、斜面貝層の小崩落(高濃度流発生)が多発していたと考えられ、現状ではセクション図掲載分層の断面間対比は部分的にしかできていません。


2026年6月23日火曜日

考古学切手 エヴォラ・ローマ神殿

 Archaeology Stamp: The Roman Temple of Évora


I recently revisited a stamp from a collection I started around 1956—the only one in the set featuring an archaeological site—and enjoyed it all over again after 70 years. It is a stamp from Portugal (issued in 1935) depicting the Roman Temple of Évora. It brought back memories of visiting the site during a two-week trip to Spain and Portugal in 2009.


1956年頃収集した切手に1枚だけ考古学切手が含まれていたので、70年経った今、再び楽しみました。ポルトガル(1935年発行)のエヴォラ・ローマ神殿切手です。2009年スペインポルトガル2週間旅行で訪問したことを思い出しました。

1 エヴォラ・ローマ神殿 ポルトガル発行


エヴォラ・ローマ神殿 ポルトガル発行

1935年

ChatGPT説明:通称:ディアナ神殿。紀元1世紀頃のローマ帝国ルキウス・アウグストゥス帝の時代に建てられたもので、世界遺産にも登録されている歴史的遺構。


エヴォラ・ローマ神殿(2009.05撮影)


エヴォラ・ローマ神殿(2009.05撮影)

切手から、2009年スペインポルトガル2週間旅行でエヴォラ・ローマ神殿を訪問したことを思い出しました。

2 ヴィクトリア女王即位50周年記念普通切手 イギリス発行


ヴィクトリア女王即位50周年記念普通切手 イギリス発行

1887年

ChatGPT説明:この切手は当時、新聞や絵葉書の郵送用に大量に印刷・使用されたため、現在でも消印付き(使用済み)のものは非常に多く残っています。

この切手の消印は1895年8月7日と読めます。

この切手は考古学切手ではありませんが、70年前に自分が収集した切手で最も価値の高いものだと感じ、とても丁寧に保存してきたものです。わらしべ長者になった気分でした。今般ChatGPTの情報によると、希少性はあまりないようですが、愛着のある切手です。


考古学切手 ネフェルティティ胸像 その2

 Archaeology Stamps: Bust of Nefertiti (Part 2)


I enjoyed discovering another stamp featuring the bust of Nefertiti in my archaeology stamp collection. It is one of the stamps that make up a souvenir sheet titled "Egypt Under the Pharaohs," issued by Mozambique.


考古学切手マイコレクションから新たにネフェルティティ胸像切手を見つけて楽しみました。モザンビーク発行「ファラオの下のエジプト」小型シートの一枚を構成する切手です。

この記事は2026.06.10記事「考古学切手 ネフェルティティ胸像」の続きです。

1 ネフェルティティ胸像 モザンビーク発行


ネフェルティティ胸像 モザンビーク発行

2014年

ポルトガル語の説明文: 「O busto de Nefertiti foi a grande esposa real, e data para cerca de 1370 aC - 1330 aC(ネフェルティティの胸像は偉大なる王の正妃であり、紀元前1370年頃〜紀元前1330年頃のものです)」

2 「ファラオの下のエジプト」小型シート モザンビーク発行


「ファラオの下のエジプト」小型シート モザンビーク発行

2014年

ネフェルティティ胸像切手の隣の切手のChatGPT説明:太陽神アテン(天体としての太陽)の光を浴びる、ファラオ・アクエンアテン(アメンホテプ4世)と王妃ネフェルティティ、そして子供たちの姿が彫られたレリーフ。 

3 メモ

2026.06.10記事を書くときになぜこの切手を見落としたのか、自分でも不思議です。エジプト切手が4000枚(1/3が考古学切手)未整理で存在していて、それを丹念に確認することに気がとられていたようです。


第1断面谷頭部の遺物分布ヒートマップ

 Heat Map of Artifact Distribution at the Head of the Valley (Section 1)


I created a heat map showing the distribution of pottery, stone tools, and bone/tooth artifacts at the head of the valley in Section 1 of the shell midden located on the northern slope of the Ariyoshi-Kita Shell Midden. The map reveals a concentration of artifacts near the area of ​​the shell layer situated in the middle section of the stratigraphy. This distribution map provides valuable information suggesting that shells, pottery, stone tools, and bone/tooth artifacts were all discarded from the edge of the plateau down to the same location at the base of the cliff.


有吉北貝塚北斜面貝層の第1断面谷頭部の土器、石器、骨歯の分布ヒートマップを作成しました。層序中段の貝層分布域付近に遺物分布が集中しているように観察できます。この分布図は、貝殻と土器・石器・骨歯が台地面縁から崖下の同じ場所に投げ込まれたと推測できる貴重な情報です。

1 第1断面谷頭部の遺物分布ヒートマップ


第1断面谷頭部土器分布ヒートマップ


第1断面谷頭部石器分布ヒートマップ


第1断面谷頭部骨歯分布ヒートマップ


第1断面谷頭部土器石器骨歯分布ヒートマップ


参考 第1断面谷頭部貝層区分(区分は発掘調査報告書による)

2 メモ

2-1 層序中段の混土貝層付近に遺物が集中

層序中段の混土貝層分布域付近に遺物分布が集中しているように観察できます。これらの分布図は、貝殻と土器・石器・骨歯が台地面縁から崖下の同じ場所に投げ込まれたと推測できる貴重な情報です。

2-2 基底混土貝層付近に遺物がない

基底混土貝層付近に遺物がほとんど分布しません。谷頭部ができた直後は貝殻の投棄はしたけれども土器、石器、骨歯の投棄はほとんどなかったことになります。なぜそうなのか、その理由に興味が深まります。

2026年6月22日月曜日

技術メモ pngファイルからヒートマップを作成するPythonスクリプトの改良

 Technical Notes: Improvements to a Python Script for Creating Heatmaps from PNG Files


I've been using a very convenient Python script to create heatmaps from dot distribution images (transparent background PNG files). However, I encountered some issues, so I've improved it. The resulting Python script is now more robust.


ドット分布画像(背景透明pngファイル)からヒートマップを作成するPythonスクリプトをとても便利に使っています。しかし、不都合が生じたので改良しました。より堅牢なPythonスクリプトとなりました。

この記事は2026.06.03記事「技術メモ pngファイルから直接ヒートマップを作成するPythonスクリプト」の続きになります。

1 遭遇した不都合


遭遇した不都合

これまで順調にpngファイルからPythonスクリプトで直接ヒートマップを作成してきました。しかし、上記のような不都合に遭遇しました。

調べたところ、画像の透明部分にRGBの色が残っている部分が存在しているために生じた現象であることがわかりました。Pythonスクリプトは透明か否かではなく、色がある部分=点群データとして扱っているので生じた不都合です。

2 不都合の解決策

この不都合の解決策として、次の2を執りました。

1 画像透明部から色を完全に除去する新たなPythonスクリプト作成。

2 色のある透明部を間違って拾わないヒートマップ作成Pythonスクリプトの作成。

それぞれのPythonスクリプトを作成しましたが、2の新たな改良ヒートマップ作成Pythonスクリプトを使う方が作業の手間がはぶけます。ヒートマップ作成Pythonスクリプトがより堅牢となりました。

3 改良Pythonスクリプト (透明部に色が残っていても正常に作動する改良版)


from pathlib import Path

import cv2
import numpy as np
from PIL import Image
from matplotlib import cm

# =========================
# 設定
# =========================

input_path = r"G:/test/aaa.png"
output_path = r"G:/test/aaa_heatmap.png"

# 使用する単色グラデーション
# 例: "Blues", "Reds", "Greens", "Purples", "Oranges", "Greys"
color_map_name = "Reds"

# グラデーションの区分数
# 例: 5, 8, 10, 16, 32
gradient_steps = 10

# 密度計算のぼかし半径
# 大きいほど広くなめらかな密集度になる
blur_radius = 20

# 背景を透明にするか
transparent_background = False

# 点群抽出に使うアルファ閾値
# alpha > alpha_threshold のピクセルだけを点として扱う
# 0: わずかでも不透明なら点
# 10〜30: ほぼ透明なノイズを除外
alpha_threshold = 20

# ヒートマップの透明出力時、薄い密度部分を透明にする閾値
# 通常は 0.0 のままでOK
heat_alpha_threshold = 0.0

# =========================
# 処理
# =========================

input_file = Path(input_path)
if not input_file.exists():
    raise FileNotFoundError(f"画像が読み込めません: {input_path}")

# RGBAで読み込み、RGB値には依存しない
img = Image.open(input_file).convert("RGBA")
arr = np.array(img)

# アルファチャンネルだけで点群を抽出
alpha = arr[:, :, 3]
dot_mask = alpha > alpha_threshold

point_pixels = int(np.count_nonzero(dot_mask))
if point_pixels == 0:
    raise ValueError(
        "アルファ値から点を検出できませんでした。"
        f" alpha_threshold={alpha_threshold} を下げてください。"
    )

print(f"画像サイズ: {img.size[0]} x {img.size[1]}")
print(f"alpha min/max: {int(alpha.min())} / {int(alpha.max())}")
print(f"点として使うピクセル数: {point_pixels}")

# 密度画像を作成
density = dot_mask.astype(np.float32)

# ガウシアンぼかしで密集度を計算
density = cv2.GaussianBlur(
    density,
    ksize=(0, 0),
    sigmaX=blur_radius,
    sigmaY=blur_radius,
)

# 0〜1に正規化
max_density = float(density.max())
if max_density > 0:
    density = density / max_density

# グラデーションを段階化
density_step = np.floor(density * gradient_steps) / gradient_steps
density_step = np.clip(density_step, 0, 1)

# カラーマップ適用
cmap = cm.get_cmap(color_map_name)
heat_rgba = cmap(density_step)

# 0〜255へ変換
heat_img = (heat_rgba[:, :, :3] * 255).astype(np.uint8)

# 密度ゼロ部分の処理
if transparent_background:
    out_alpha = (density > heat_alpha_threshold).astype(np.uint8) * 255
    output = np.dstack([heat_img, out_alpha])
    Image.fromarray(output, mode="RGBA").save(output_path)
else:
    # 背景は白
    heat_img[density <= heat_alpha_threshold] = [255, 255, 255]
    Image.fromarray(heat_img, mode="RGB").save(output_path)

print(f"ヒートマップを書き出しました: {output_path}")

2026年6月20日土曜日

考古学切手 兵馬俑 その1

 Archaeological Stamps: Terracotta Army, Part 1


I enjoy collecting archaeological stamps featuring the Terracotta Army from my personal collection. These are four older stamps issued in 1983. They were out of stock at stamp dealers, so I acquired them through Mercari.


兵馬俑をテーマとした考古学切手をマイコレクションの中から集めて楽しんでいます。最初に1983年発行の古い切手4種です。切手商には在庫がなく、メルカリで入手しました。

1 兵馬俑 T.88.(4-1) 中国発行


兵馬俑 T.88.(4-1) 中国発行

1983年

特種切手「秦始皇帝兵馬俑(カタログ番号:T88)」の4種セットのうちの1枚

2 兵馬俑 T.88.(4-2) 中国発行


兵馬俑 T.88.(4-2) 中国発行

1983年

特種切手「秦始皇帝兵馬俑(カタログ番号:T88)」の4種セットのうちの1枚

3 兵馬俑 T.88.(4-3) 中国発行


兵馬俑 T.88.(4-3) 中国発行

1983年

特種切手「秦始皇帝兵馬俑(カタログ番号:T88)」の4種セットのうちの1枚

4 兵馬俑 T.88.(4-4) 中国発行


兵馬俑 T.88.(4-4) 中国発行

1983年

特種切手「秦始皇帝兵馬俑(カタログ番号:T88)」の4種セットのうちの1枚

5 メモ

よく見かける兵馬俑の切手であり、直ぐにでも入手できると楽観していました。しかし、どの切手商にも在庫がなく、入手をあきらめかけていました。あるとき、フト、メルカリの存在に気が付き検索したところ、格安で即入手できました。それ以来切手入手方法としてメルカリを多用するようになりました。

メルカリ販売者(過去のお土産品などの不要物処分)←→私(考古学切手として入手したい価値ある小品)という対照が面白いです。

話しは脱線しますが、古い考古学図書の入手にヤフオクを利用したことあります。縄文土器大観全4巻を、うまく立ち回り、格安で入手できた体験があります。しかし、ヤフオクを使ってみて、無意味に他の人と競うという心を磨り減らす側面があり、それ以来止めました。それに対して、メルカリは心の負担なしに価値交換できるので、よりよい仕組みです。