') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - interactivereport/FundusNet: FundusNet: A Deep-Learning Approach for Fast Diagnosis of Neurodegenerative and Eye Diseases Using Fundus Images. · GitHub
Skip to content

Repository files navigation

FundusNet

FundusNet: a deep learning approach for identifying novel endophenotypes for neurodegenerative and eye diseases from fundus images

image

Hu, W., Li, K., Gagnon, J., Wang, Y., Raney, T., Chen, J., Chen, Y., Okunuki, Y., Chen, W., & Zhang, B. (2025). FundusNet: A Deep-Learning Approach for Fast Diagnosis of Neurodegenerative and Eye Diseases Using Fundus Images. Bioengineering, 12(1), 57. https://doi.org/10.3390/bioengineering12010057

Steps:

  1. Install cuda
  2. conda create --name <name_of_your_env> or conda create -p <path_of_your_env>
    conda activate <name_of_your_env>
    Install pytorch https://pytorch.org/get-started/locally/
    Example, pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu126
  3. git clone the repo
    git clone https://github.com/interactivereport/FundusNet.git
  4. install packages
    pip install -r requirements.txt
  5. Execute either shgender.sh or shage.sh to run individual CNN or ViT models:
    a. This process will split the image dataset into training and testing sets, train the CNN/ViT models on the training data, and evaluate them on the test data.
    b. Users must provide the following inputs:
    'name of csv_file (string)': Path to the CSV file containing annotations.
    'root_dir (string)': Directory containing all images.
  6. Combine the results using majority voting for ensemble prediction.

A tutorial for result interpretation using gradCAM:

importosimportnumpyasnpimportmathimporttorchfrompytorch_grad_camimportGradCAMfrommodelwrapperimportModelwrapperfromgcam_utilsimportrun_save_gcam_results
# specify input filesdevice=torch.device("cuda:0"iftorch.cuda.is_available() else"cpu")
model_name='regnety_32'pheno_name='glaucoma'# examples of model checkpoints, https://zenodo.org/records/15711816cmodel_ckpt=f'../model_ckpt/{pheno_name}_{model_name}_v0.pth'csv_file=f'../result_biomarker_interpretation/input_imgs/imgs_{pheno_name}.csv'img_dir='../result_biomarker_interpretation/input_imgs/'result_dir='../result_biomarker_interpretation/'
# run gradCAM to generate figures for biomarker interpretationnum_classes=1ifpheno_name=='age'else2mwrapper=Modelwrapper(num_classes=num_classes)
cmodel=getattr(mwrapper, model_name)().to(device)
cmodel.load_state_dict(torch.load(cmodel_ckpt))
cmodel.eval()
target_layers= [cmodel.s4.b1.conv3.conv]
use_cuda=Trueifdevice.type=='cuda'elseFalsecam=GradCAM(model=cmodel, target_layers=target_layers, use_cuda=True)
run_save_gcam_results(cam=cam,
img_dir=img_dir,
result_dir=result_dir,
csv_file=csv_file,
pheno_name=pheno_name)
['image_0_glaucoma_1.png', 'image_1_glaucoma_1.png', 'image_2_glaucoma_1.png', 'image_3_glaucoma_1.png']
['image_4_glaucoma_0.png', 'image_5_glaucoma_0.png', 'image_6_glaucoma_0.png', 'image_7_glaucoma_0.png']

png

png

A tutorial for predicting age and gender:

importosimportnumpyasnpimportpandasaspdimporttorchfrommodelwrapperimportModelwrapperfromimgdatasetimportImgDataset_withaugment
# specify input filesdevice=torch.device("cuda:0"iftorch.cuda.is_available() else"cpu")
model_name='regnety_32'pheno_name='age'# 'gender'cmodel_ckpt=f'../model_ckpt/{pheno_name}_{model_name}_v0.pth'csv_file=f'../result_biomarker_interpretation/input_imgs/imgs_{pheno_name}.csv'img_dir='../result_biomarker_interpretation/input_imgs/'result_dir='../result_biomarker_interpretation/'batchsize=2
num_classes=1ifpheno_name=='age'else2mwrapper=Modelwrapper(num_classes=num_classes)
cmodel=getattr(mwrapper, model_name)().to(device)
cmodel.load_state_dict(torch.load(cmodel_ckpt))
cmodel.eval()
image_datasets=ImgDataset_withaugment(csv_file=csv_file, root_dir=img_dir, crop='center')
dataloader=torch.utils.data.DataLoader(image_datasets, batch_size=batchsize, num_workers=4)
dataloader_iterator=iter(dataloader)
img_names= []
y_true= []
y_preds= []
forinputs, labels, img_nameindataloader:
img_names.extend(list(img_name))
y_true.extend(labels.tolist())
inputs=inputs.to(device)
labels=labels.to(device)
preds=cmodel(inputs)
ifnum_classes==2:
_, preds=torch.max(preds, 1)
y_preds.extend(preds.cpu().detach().numpy().flatten())
df=pd.DataFrame({'filename':img_names, 'class':y_true, f'predicted_{pheno_name}':y_preds})
df.to_csv(os.path.join(result_dir, f'imgs_{pheno_name}_predicted.csv'), index=False)
print(df.head())
 filename class predicted_age
0 image_0_age_61.png 61 58.069225
1 image_1_age_61.png 61 62.588211
2 image_2_age_72.png 72 73.754265
3 image_3_age_53.png 53 53.196304

About

FundusNet: A Deep-Learning Approach for Fast Diagnosis of Neurodegenerative and Eye Diseases Using Fundus Images.

Resources

Stars

8 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages