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
28 changes: 25 additions & 3 deletions brainstat/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def deprecated_func(*args, **kwargs):
def _download_file(
url: str, output_file: Path, overwrite: bool = False, verbose=True
) -> None:
"""Downloads a file.
"""Downloads a file with retry logic for network failures.

Parameters
----------
Expand All @@ -163,14 +163,36 @@ def _download_file(
verbose : bool
If true, print a download message, defaults to True.
"""
import time
from http.client import RemoteDisconnected
from urllib.error import URLError

if output_file.exists() and not overwrite:
return

if verbose:
logger.info("Downloading " + str(output_file) + " from " + url + ".")
with urllib.request.urlopen(url) as response, open(output_file, "wb") as out_file:
shutil.copyfileobj(response, out_file)

# Retry logic for intermittent network failures
max_retries = 3
retry_delay = 2 # seconds

for attempt in range(max_retries):
try:
with urllib.request.urlopen(url, timeout=30) as response, open(output_file, "wb") as out_file:
shutil.copyfileobj(response, out_file)
return # Success, exit function
except (RemoteDisconnected, URLError, TimeoutError) as e:
if attempt < max_retries - 1:
logger.warning(
f"Download attempt {attempt + 1}/{max_retries} failed: {e}. "
f"Retrying in {retry_delay} seconds..."
)
time.sleep(retry_delay)
retry_delay *= 2 # Exponential backoff
else:
logger.error(f"Download failed after {max_retries} attempts.")
raise # Re-raise the exception after all retries fail



Expand Down
15 changes: 15 additions & 0 deletions brainstat/context/histology.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ def read_histology_profile(

with h5py.File(histology_file, "r") as h5_file:
if template == "fslr32k":
# Known issue #369: The fslr32k data file on the server actually contains
# fs_LR_64k resolution data (64984 vertices) instead of fs_LR_32k (32492 vertices).
# This is a data hosting issue. Users expecting 32k resolution data should be aware
# that they will receive 64k resolution data until this is fixed on the server.
logger.warning(
"Known issue: The fslr32k histology profile data currently contains "
"fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k (32492 vertices). "
"See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
)
profiles = h5_file.get("fs_LR_64k")[...]
else:
profiles = h5_file.get(template)[...]
Expand Down Expand Up @@ -196,6 +205,12 @@ def download_histology_profiles(
data_dir.mkdir(parents=True, exist_ok=True)
if template == "fslr32k":
output_file = data_dir / "histology_fslr32k.h5"
# Known issue #369: warn users about data resolution mismatch
logger.warning(
"Note: The fslr32k histology profile data currently contains "
"fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k. "
"See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
)
else:
output_file = data_dir / ("histology_" + template + ".h5")

Expand Down
3 changes: 0 additions & 3 deletions brainstat/tests/test_histology.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ def test_urls(template):
template : list
Template names.
"""
if template == "fslr32k":
pytest.skip("Skipping fslr32k due to known netneurotools 0.3.0 Unicode bug")

try:
r = requests.head(json["bigbrain_profiles"][template]["url"], timeout=10)
assert r.status_code == 200
Expand Down