Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ experiments/journal_revision/results/**/.evaluation.lock
/experiments/journal_revision/backbone_graph_comparison/evaluation/**/GENES.csv.gz

/experiments/journal_revision/backbone_graph_comparison/primary_evaluation/**/GENES.csv.gz

# Explicit replacement checkpoints and replay bulk output: separate from originals.
/checkpoints/reproduced/
/reproduction/runs/
18 changes: 18 additions & 0 deletions configs/reproduction/primary-v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"schema_version": 1,
"study": "historical_primary_resnet18_retraining_v1",
"origin": "RETRAINED_NOT_ORIGINAL",
"train": ["NCBI783", "NCBI785"],
"validation": ["TENX95"],
"development_evaluation_only": ["TENX99"],
"seeds": [0, 1, 2],
"model": {"hidden": 256, "dropout": 0.1},
"training": {"learning_rate": 0.001, "weight_decay": 0.0001, "batch_size": 512, "max_epochs": 80, "patience": 10, "min_delta": 0.0001},
"threads": 4,
"feature_scaler": "checkpoints/legacy/C4/C4A/preprocessing/resnet18_reference/primary/S2-50/feature.npz",
"target_scaler": "checkpoints/legacy/C4/C4A/preprocessing/resnet18_reference/primary/S2-50/target.npz",
"normalization": "checkpoints/legacy/reports/C0/C0_NORMALIZATION.json",
"transport": "checkpoints/legacy/C9/C9A/T1_SCALER.json",
"historical_details": "manuscript/revised/methods_data/primary_model_details.json",
"agreement": {"median_pearson_absolute": 0.05, "relative_mean_mse": 0.15, "relative_mean_mae": 0.15}
}
44 changes: 44 additions & 0 deletions docs/RELEASES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Verified research artifacts v1

[Public research release](https://github.com/HySonLab/GraphPathology/releases/tag/research-artifacts-v1) — 15 assets; 706,647,760 bytes. All asset sizes/SHA256 values match local staging and GitHub digests; all 15 URLs responded without credentials. The primary-head ZIP, README and verification receipt were additionally downloaded anonymously and rehashed.

Code tag: research-artifacts-v1 → 44918ef8f9e9876234ceb02a8b4a815d8b4072d4. This is the tested commit on the separate, unmerged [PR #4](https://github.com/HySonLab/GraphPathology/pull/4), not a change to main. Later PR commits add download/publication receipts and the auditable clean-checkout driver; the packaged inference/training code is unchanged.

Public artifacts contain 99 distinct best states: three RETRAINED_NOT_ORIGINAL primary replacements and 96 recovered ResNet18-based exploratory states. Forty-eight resume files are additional files, not extra fitted models. No raw patient data, pretrained encoder or Phikon head is included. The original primary identities remain missing, and the original doctor still fails as expected.

## Downloads

- [graphpathology-retrained-primary-resnet18-heads-v1.zip](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/graphpathology-retrained-primary-resnet18-heads-v1.zip) — 1,635,176 bytes.
- [graphpathology-exploratory-neural-heads-v1.zip](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/graphpathology-exploratory-neural-heads-v1.zip) — 114,543,063 bytes.
- [graphpathology-source-data-results-v1.zip](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/graphpathology-source-data-results-v1.zip) — 587,712,241 bytes.
- [graphpathology-model-and-results-manifest-v1.json](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/graphpathology-model-and-results-manifest-v1.json) — 738,362 bytes.
- [SHA256SUMS.txt](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/SHA256SUMS.txt) — 1,230 bytes.
- [RELEASE_VERIFICATION.json](https://github.com/HySonLab/GraphPathology/releases/download/research-artifacts-v1/RELEASE_VERIFICATION.json) — 24,786 bytes.

The complete 15-asset inventory, stable URLs, sizes and SHA256 hashes— including the checksum index and verification receipt themselves— is in reproduction/RELEASE_ASSETS.json. SHA256SUMS.txt covers the initial archives/metadata/manifest; it cannot hash itself and predates the later receipt. The receipt’s own hash is verified against GitHub’s digest and recorded in RELEASE_ASSETS.json. Checksums are integrity checks, not author signatures.

## Actual clean-checkout test

All 14 initial assets (706,622,974 bytes) were downloaded from the actual authenticated draft asset IDs into a separate directory. A fresh GitHub clone of the exact tag restored the downloaded archives, preflighting every member and refusing existing conflicts. All 147 public PT files loaded with weights_only=True; all 96 recovered public heads performed real 554-location BASS_P19 inference. The three replacement heads additionally ran on eight real HEST image patches using a fresh official 46,830,571-byte ResNet18 download; its SHA256 matched and extracted features were byte-identical to the recovered reference. All 83,136 frozen files and original scientific Git trees passed in that checkout; tracked files remained unchanged.

The same pinned Python environment on the same host was reused; this is not a claim of independent hardware/environment or newly acquired evaluation data. Exact inputs, file hashes, outputs and scope are in reproduction/CLEANROOM_VERIFICATION.json and RELEASE_VERIFICATION.json. The auditable driver is reproduction/CLEANROOM_PROBE.py; point PYTHONPATH at the tagged checkout and supply the documented registered local inputs. Dataset/encoder acquisition is separate from artifact redistribution.

## Restore

Follow reproduction/README_RELEASE.md and requirements-v1.txt. From a fresh editable checkout, download the exact release, check SHA256SUMS.txt, then run:

```bash
python -m graphpathology.reproduction_release restore \
--assets /absolute/path/to/downloads \
--manifest /absolute/path/to/downloads/graphpathology-model-and-results-manifest-v1.json
```

Use the explicit reproduction_primary route for replacement heads; do not rename them into historical original paths. Read the original ordered genes/scalers/configuration from the supplied manifest. Fresh run identities are required for any rerun.

## Phikon draft and unresolved rights

[Metadata-only Phikon rights-review draft](https://github.com/HySonLab/GraphPathology/releases/tag/untagged-84e85d779dcbe207b3ab) (authenticated maintainer access; intentionally unpublished). Its only asset is PHIKON_RIGHTS_REVIEW.md. Thirty-six verified head binaries and their derivative numerical archives remain local; none was uploaded to this draft or the public release. Owkin’s custom nonprofit/transfer/same-terms Results provisions and HEST’s separate share-alike conditions require clarified compatible onward-distribution rights. See reproduction/LICENSE_AUDIT.md for exact pinned sources and hashes. No blanket software or manuscript license was changed.

## Scientific preservation

No manuscript, supplementary material, figure, table, bibliography, PDF, historical result or reported number changed. Original trees are byte-identical; main remains 67738be6f0aaace07b55afac44db3d3d23c41c9b. The PR stays unmerged. Exact deterministic replay, approximate new fitting, scoped artifact checks, seed variation, source/runtime sensitivity and failures are separately documented in reproduction/REPRODUCTION_REPORT.md, RESULTS_COMPARISON.csv, INVENTORY.csv and verification/. No paid computing service was used.
137 changes: 137 additions & 0 deletions reproduction/ARCHIVE_SEARCH.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
[
{
"archive": "/home/hytruongson/Downloads/1. Huu Phong Nguyen.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/Multi_Agent_system_transform_SQL_to_Knowledge_Graph_database.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/For-anh-Tung.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/MOB_GCN__Frontiers_.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/GraphPathology_arXiv_Compact_Source.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/amp-atlas-arxiv.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/JCIM___LINKER.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/AMP_Atlas.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/Multi_Agent_AI_project__Rebin_(1).zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/NIH_R21_Full_Proposal.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/GraphPathology_Codex_Paper_Revision_Handoff_v2.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/PRIME.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/Multi_Agent_AI_project__Rebin_.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/Q_BioLat_BMC_Bioinformatics_table_refs_fixed.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/skempi2_preprocessed.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/Biological_Reasoning___Phuc_s_Survey.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Downloads/MOB_GCN_Frontiers_fixed.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Approved_Submission_Source.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Transformer_EGNN_New_Heads.zip",
"model_members": 96,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Final_Minor_Polish_Source.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Matched_Backbone_Head_States.zip",
"model_members": 72,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Matched_Backbone_Numerical_Results.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Journal_v2_New_Heads.zip",
"model_members": 30,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Transformer_EGNN_Full_Gene_Exports.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Matched_Backbone_Submission_Source.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Scientific_Reports_Submission_Source.zip",
"model_members": 0,
"matching_originals": []
},
{
"archive": "/home/hytruongson/Documents/Codex/2026-10-31/pleas/outputs/GraphPathology_Transformer_EGNN_Clean_Source.zip",
"model_members": 0,
"matching_originals": []
}
]
107 changes: 107 additions & 0 deletions reproduction/CLEANROOM_PROBE.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Auditable driver for a fresh checkout; imports only the tagged checkout's code.

The probe uses the existing pinned dependency environment and two registered real
inputs read locally. It does not claim independent hardware or fresh data acquisition.
"""
import argparse
import hashlib
import importlib.metadata
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time

p=argparse.ArgumentParser(description=__doc__)
p.add_argument('--checkout',type=Path,required=True)
p.add_argument('--source-inputs',type=Path,required=True)
p.add_argument('--downloads',type=Path,required=True)
p.add_argument('--encoder',type=Path,required=True)
p.add_argument('--output',type=Path,required=True)
a=p.parse_args();root=a.checkout.resolve();source=a.source_inputs.resolve()
if a.output.exists():raise FileExistsError(a.output)
sys.path.insert(0,str(root/'src'))
import graphpathology
assert Path(graphpathology.__file__).resolve().is_relative_to(root/'src')
import h5py
import numpy as np
from PIL import Image
import torch
from torchvision.models import resnet18,ResNet18_Weights
from graphpathology.assets import sha
from graphpathology.reproduction_primary import image_predictions
from graphpathology.reproduction_release import restore,preservation
from graphpathology.extension_evaluate import model_inventory,load_data,prediction
from graphpathology.reproduction_artifacts import matched_prediction

started=time.monotonic();torch.set_num_threads(4);torch.use_deterministic_algorithms(True)
manifest=json.loads((a.downloads/'graphpathology-model-and-results-manifest-v1.json').read_text())
commit=subprocess.check_output(['git','rev-parse','HEAD'],cwd=root,text=True).strip()
assert commit==manifest['code_commit']=='44918ef8f9e9876234ceb02a8b4a815d8b4072d4'
restored=restore(root,a.downloads,manifest)
expected_env=json.loads((root/'reproduction/ENVIRONMENT.json').read_text())
for package,version in expected_env['packages'].items():assert importlib.metadata.version(package)==version,(package,version)
# Every restored public PT file, including optimizer/resume snapshots, is loaded safely.
pt={name for asset in manifest['assets'] for name in asset.get('files',{}) if name.endswith('.pt')}
assert len(pt)==147
for name in sorted(pt):torch.load(root/name,map_location='cpu',weights_only=True)

# This real554-location cache is copied locally, not downloaded or redistributed.
patient='BASS_P19';relative=Path('data/journal_revision/transformer_egnn/bassiouni')/(patient+'.npz')
for path in [relative,relative.with_suffix('.json')]:
target=root/path;target.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(source/path,target)
data=load_data(root,patient);assert len(data['barcodes'])==554
seen=set();forwards=[]
for item in model_inventory(root):
image=prediction(root,data,patient,item)
assert image.shape==(554,50) and np.isfinite(image).all()
seen.add(item['checkpoint']);forwards.append({'checkpoint':item['checkpoint'],'rows':554,'finite':True})
items=json.loads((root/'experiments/journal_revision/backbone_graph_comparison/ALL_CANDIDATE_FIT_MANIFEST.json').read_text())
for item in items:
if item['backbone']!='resnet18' or item['checkpoint'] in seen:continue
_,image=matched_prediction(root,patient,item)
assert image.shape==(554,50) and np.isfinite(image).all()
seen.add(item['checkpoint']);forwards.append({'checkpoint':item['checkpoint'],'rows':554,'finite':True})
assert len(seen)==96

# Fresh official encoder bytes were downloaded into the clean-room workspace.
encoder_sha=sha(a.encoder)
assert encoder_sha=='f37072fd47e89c5e827621c5baffa7500819f7896bbacec160b1a16c560e07ec'
net=resnet18(weights=None);net.load_state_dict(torch.load(a.encoder,map_location='cpu',weights_only=True));net.fc=torch.nn.Identity();net.eval();net.requires_grad_(False)
base=source/'data/journal_revision/hest_training'
cache=base/'processed/journal_v2_hest/NCBI783.npz'
meta=json.loads(cache.with_suffix('.json').read_text())
patch=base/'raw/hest/IDC/patches/NCBI783.h5';assert sha(patch)==meta['source_patches_sha256']
with h5py.File(patch) as f:
rgb=f['img'][:8];barcodes=f['barcode'][:8].reshape(-1).astype(str)
transform=ResNet18_Weights.IMAGENET1K_V1.transforms()
batch=torch.stack([transform(Image.fromarray(im)) for im in rgb])
with torch.inference_mode():features=net(batch).numpy()
with np.load(cache,allow_pickle=False) as f:
reference=f['embeddings'][:8];np.testing.assert_array_equal(barcodes,f['barcodes'][:8])
# Locked before this probe: tolerance for float32 batch-shape convolution reductions.
np.testing.assert_allclose(features,reference,atol=1e-5,rtol=1e-5)
folder=root/'checkpoints/reproduced/primary_resnet18/primary-20261101-v1'
image,_=image_predictions(folder,features)
assert image.shape==(8,50) and image.dtype==np.float32 and np.isfinite(image).all()
env={**os.environ,'PYTHONPATH':str(root/'src')}
doctor=subprocess.run([sys.executable,'-m','graphpathology','--root',str(root),'doctor','--require-models'],cwd=root,env=env,capture_output=True,text=True)
assert doctor.returncode==1
state=json.loads(doctor.stdout);assert len(state['primary_heads'])==3 and not any(r['available'] for r in state['primary_heads'])
protected=preservation(root);assert protected['status']=='PASS'
subprocess.run(['git','diff','--exit-code','HEAD','--'],cwd=root,check=True,capture_output=True)
result={'status':'PASS','code_commit':commit,'remote':'https://github.com/HySonLab/GraphPathology.git',
'code_imported_from_clean_checkout':True,'dependency_environment':'Existing pinned venv reused on the same machine; not an independent hardware/environment replication.',
'restore':restored,'safe_public_pt_files':len(pt),'recovered_unique_heads_forwarded':len(seen),'recovered_forward_input':'Actual BASS_P19,554 registered locations; cache copied locally and hash-verified.',
'encoder_url':'https://download.pytorch.org/models/resnet18-f37072fd.pth','encoder_bytes':a.encoder.stat().st_size,'encoder_sha256':encoder_sha,
'primary_real_image_rows':8,'primary_output_shape':list(image.shape),'primary_output_finite':True,
'primary_output_array_sha256':hashlib.sha256(image.tobytes()).hexdigest(),
'fresh_encoder_feature_max_abs_delta':float(np.max(abs(features-reference))),
'fresh_encoder_feature_tolerance':{'atol':1e-5,'rtol':1e-5},'real_input_patch_sha256':sha(patch),
'original_doctor_expected_missing_exit_code':doctor.returncode,'preservation':protected,
'tracked_checkout_files_unchanged':True,'probe_script_sha256':sha(Path(__file__)),
'elapsed_seconds':time.monotonic()-started,'forwards':forwards}
a.output.parent.mkdir(parents=True,exist_ok=True);a.output.write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps({k:v for k,v in result.items() if k not in ['forwards','preservation']},indent=2))
Loading
Loading