P-Tree — Himawari-9 AHI HSD full-disk download¶
Download one 10-minute observation of the Himawari-9 AHI B03 (0.5 km visible) full-disk imagery from JAXA's P-Tree FTP archive. HSD full-disk is delivered as 10 segment files per band per 10-minute slot (S0110…S1010); the backend fetches all 10 and mirrors them under out_dir/YYYYMM/DD/HH/ so several timeslots don't collide on filenames.
No SDK extra needed — the ptree branch is stdlib ftplib only, so a plain pip install earthlens is enough. The download step needs $JAXA_PTREE_USERNAME + $JAXA_PTREE_PASSWORD (free account at https://www.eorc.jaxa.jp/ptree/registration_top.html — separate registration from G-Portal); without those it falls back to a printed catalog inspection.
Retention: P-Tree serves only the last 30 days of HSD. This notebook always requests "yesterday 00:00 UTC" so the window is inside the archive.
Licence: since 2026-02-01 P-Tree data is available for commercial use (attribution per the Terms of Use).
Decode: this backend ships the raw .DAT.bz2 granules. Decoding HSD to arrays is satpy's job (tracked as pyramids PY-2) — do not expect a GeoTIFF here.
Inspect the catalog row¶
from earthlens.jaxa import Catalog
cat = Catalog()
row = cat.get('himawari-ahi-fldk')
print('canonical key:', row.key)
print('aliases: ', row.aliases)
print('protocol: ', row.protocol)
print('default_band: ', row.default_band)
print('description: ', row.description)
print()
print('by_protocol("ptree"):', cat.by_protocol('ptree'))
Pick a window inside the 30-day archive¶
One 10-minute slot at yesterday 00:00 UTC — inside the archive (the 30-day boundary is D-30 inclusive) and small enough to fit in a few minutes over a home connection. Expected download size for B03 (0.5 km visible, R05): ~250-300 MB per slot (10 segments, each 12-36 MB compressed). Pick an IR band such as B13 (2 km, R20) instead for a ~10 MB per-slot download.
import datetime as dt
yesterday = dt.datetime.now(dt.UTC) - dt.timedelta(days=1)
slot = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
start = slot.strftime('%Y-%m-%d %H:%M')
# Match the notebook's example: one 10-min slot -> start == end (floored).
end = start
print(f'window: {start} .. {end} (UTC)')
print('expected: 10 files (all segments S01/10..S10/10 of B03 at that slot)')
Live download (skips when env vars absent)¶
EarthLens(data_source='jaxa', variables=['himawari-ahi-fldk'], bands=['B03'], ...) picks the ptree branch on construction, connects to ftp.ptree.jaxa.jp:21 over plain FTP (stdlib ftplib — no paramiko), and downloads each of the 10 segments into path/YYYYMM/DD/HH/.
Note: the cell below prints a SKIP line and does no work when the
JAXA_PTREE_USERNAME/JAXA_PTREE_PASSWORDenv vars are unset — this is a soft skip, not a hard error. If you see "SKIP:" in the output, set the credentials (see the JAXA authentication docs) and re-run.
import os
from pathlib import Path
OUT_DIR = Path('ptree_out').resolve()
if not (
os.environ.get('JAXA_PTREE_USERNAME') and os.environ.get('JAXA_PTREE_PASSWORD')
):
print('SKIP: $JAXA_PTREE_USERNAME + $JAXA_PTREE_PASSWORD not set.')
print(
'Register at https://www.eorc.jaxa.jp/ptree/registration_top.html and re-run.'
)
written = []
else:
from earthlens.core import EarthLens
OUT_DIR.mkdir(exist_ok=True)
lens = EarthLens(
data_source='jaxa',
variables=['himawari-ahi-fldk'],
bands=['B03'],
start=start,
end=end,
fmt='%Y-%m-%d %H:%M',
lat_lim=[-60.0, 60.0],
lon_lim=[80.0, 180.0],
temporal_resolution='hourly',
path=OUT_DIR,
)
written = lens.download()
print(f'downloaded {len(written)} segment(s) to {OUT_DIR}:')
for p in written:
print(f' {p.relative_to(OUT_DIR)} ({p.stat().st_size // 1024} KB)')
Confirm the layout matches the P-Tree server tree¶
Each downloaded file lives under YYYYMM/DD/HH/ and its filename encodes satellite, timestamp, band, resolution code (R05 for B03), and segment index. That's the same layout you'd see if you FTP'd in by hand — which makes it painless to hand off to satpy (or any other HSD reader) later.
if not written:
print('No file to inspect — re-run the previous cell with creds set.')
else:
import re
total_bytes = sum(p.stat().st_size for p in written)
print(
f'total downloaded: {total_bytes / (1024 * 1024):.1f} MB across {len(written)} files'
)
print()
# Parse one filename and print its fields.
sample = written[0]
pattern = re.compile(
r'^HS_(?P<sat>H\d\d)_(?P<ymd>\d{8})_(?P<hhmm>\d{4})_'
r'(?P<band>B\d\d)_FLDK_(?P<res>R\d\d)_(?P<seg>S\d{4})\.DAT\.bz2$'
)
m = pattern.match(sample.name)
if m:
print(f'sample: {sample.name}')
for k, v in m.groupdict().items():
print(f' {k}: {v}')
print()
print('next step — decode with satpy (pyramids PY-2):')
print(' # pip install satpy')
print(' # from satpy import Scene')
print(' # scn = Scene(filenames=[str(p) for p in written], reader="ahi_hsd")')
print(' # scn.load(["B03"])')