Automated detection of new cerebral infarctions and prognostic implications using deep learning on serial MRI.
The 3 matches
- [1] § Methods › Image data preparation and annotation ↔ 1.preprocessing.ipynb, lines 14–54 · score 0.77 · histogram matching, brain extraction, channel, coregistered, Preprocessing, ANTsPyNet
- [2] § Methods › Deep learning model development ↔ 2.ModelImplementation.ipynb, lines 198–227 · score 0.66 · random rotations, global, kernel, augmentation, stride, translations
- [3] § Methods › Deep learning model development ↔ 2.ModelImplementation.ipynb, lines 48–114 · score 0.58 · model implementation, Gradient, TensorFlow, weighted, optimization, trained
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 371 lines · 11 KB · no license · 2 matches
- # %%
- import os
- import tensorflow as tf
- import tensorflow.keras as keras
- import numpy as np
- import random
- import tensorflow.keras.backend as K
- import gc
- import natsort
- from tensorflow.keras import layers, models
- from tensorflow.keras.layers import Lambda
- # %%
- def seed_everything(seed: int = 41):
- random.seed(seed)
- np.random.seed(seed)
- os.environ["PYTHONHASHSEED"] = str(seed)
- tf.random.set_seed(seed)
- seed_everything()
- # %% [markdown]
- # **Import custom metrics and loss**
- # %%
- import CorrelationAccuracyMetric
- import CorrelationAccuracyNegMetric
- import supervised_contrastive_loss
- # %% [markdown]
- # **Hyperparameters**
- # %%
- imsize = 256
- input_shape = (imsize, imsize, 2)
- width = 128
- kernal_size = 3
- strides = 2
- encoder_lr = 1e-3
- num_epochs_encoder = 100
- classifier_lr = 1e-3
- num_epochs_classifier = 100
- encoder_trainable = False
- # %% [markdown]
- # # **Stage1. Encoder**
- # %% [markdown]
- # ## Model definition
- # %%
- class SupConModel(tf.keras.Model):
- def __init__(self, encoder, projection_head, augmenter, **kwargs):
- super(SupConModel, self).__init__(**kwargs)
- self.encoder = encoder
- self.projection_head = projection_head
- self.augmenter = augmenter
- self.loss_tracker = tf.keras.metrics.Mean(name='loss')
- self.PosCorr = CorrelationAccuracyMetric()
- self.NegCorr = CorrelationAccuracyNegMetric()
- def compile(self, optimizer, loss, **kwargs):
- super(SupConModel, self).compile(**kwargs)
- self.optimizer = optimizer
- self.loss = loss
- def call(self, inputs):
- augmented_inputs = self.augmenter(inputs)
- features = self.encoder(augmented_inputs)
- return tf.nn.l2_normalize(features, axis=1)
- @tf.function
- def train_step(self, data):
- images, labels = data
- with tf.GradientTape() as tape:
- projections = self(images, training=True)
- loss = self.loss(labels, projections)
- trainable_vars = self.encoder.trainable_weights
- gradients = tape.gradient(loss, trainable_vars)
- self.optimizer.apply_gradients(zip(gradients, trainable_vars))
- self.loss_tracker.update_state(loss)
- self.PosCorr.update_state(labels, projections)
- self.NegCorr.update_state(labels, projections)
- return {
- 'loss': self.loss_tracker.result(),
- 'corr_pos_pair': self.PosCorr.result(),
- 'corr_neg_pair': self.NegCorr.result(),
- }
- @tf.function
- def test_step(self, data):
- images, labels = data
- projections = self(images, training=False)
- loss = self.loss(labels, projections)
- self.loss_tracker.update_state(loss)
- self.PosCorr.update_state(labels, projections)
- self.NegCorr.update_state(labels, projections)
- return {
- 'loss': self.loss_tracker.result(),
- 'corr_pos_pair': self.PosCorr.result(),
- 'corr_neg_pair': self.NegCorr.result(),
- }
- @property
- def metrics(self):
- return [self.loss_tracker, self.PosCorr, self.NegCorr]
- # %% [markdown]
- # ## Data loader
- # %%
- def create_dataset(fpaths, labels, batch_size, buffer_size=1000):
- # Convert file paths and labels from lists to TensorFlow tensors
- fpaths = tf.constant(fpaths, dtype=tf.string)
- labels = tf.constant(labels, dtype=tf.float32)
- # Create a TensorFlow Dataset from the file paths and labels
- dataset = tf.data.Dataset.from_tensor_slices((fpaths, labels))
- # Define the parsing function to apply the preprocessing
- def parse_function2(fpath, label):
- # Use tf.numpy_function to load the .npy file with NumPy
- def load_npy_file(f):
- return np.load(f)
- images = tf.numpy_function(load_npy_file, [fpath], tf.float32)
- images.set_shape((imsize,imsize,2)) # Explicitly set the shape
- label = tf.cast(label, tf.float32)
- label = tf.reshape(label, [1])
- return images, label
- # Map the parsing function to the dataset
- dataset = dataset.map(parse_function2, num_parallel_calls=tf.data.AUTOTUNE)
- # Shuffle, batch, and prefetch the dataset
- dataset = dataset.batch(batch_size, drop_remainder=True)
- dataset = dataset.prefetch(buffer_size=tf.data.AUTOTUNE)
- return dataset
- # %%
- def get_all_files(folder):
- file_paths = []
- for root, dirs, files in os.walk(folder):
- for file in files:
- file_paths.append(os.path.abspath(os.path.join(root, file)))
- return file_paths
- # %%
- HP_files = natsort.natsorted(get_all_files(f'./dataset/HP'))
- HP_labels = [1]*len(os.listdir(f'./dataset/HP/Change')) + [0]*len(os.listdir(f'./dataset/HP/NoChange'))
- BP_files = natsort.natsorted(get_all_files(f'./dataset/BP'))
- BP_labels = [1]*len(os.listdir(f'./dataset/BP/Change')) + [0]*len(os.listdir(f'./dataset/BP/NoChange'))
- # %%
- patIDsHP = np.unique([fileName.split('_')[-2] for fileName in HP_files])
- trIds = patIDsHP[:int(0.7*len(patIDsHP))]
- vaIds = patIDsHP[int(0.7*len(patIDsHP)):]
- # %%
- HP_files_Tr = [fileName for fileName in HP_files if fileName.split('_')[-2] in trIds]
- HP_files_Va = [fileName for fileName in HP_files if fileName.split('_')[-2] in vaIds]
- # %%
- HP_labels_Tr = [0 if 'NoChange' in fileName else 1 for fileName in HP_files_Tr]
- HP_labels_Va = [0 if 'NoChange' in fileName else 1 for fileName in HP_files_Va]
- # %%
- temp = list(zip(HP_files_Tr, HP_labels_Tr))
- random.shuffle(temp)
- HP_files_Tr, HP_labels_Tr = zip(*temp)
- temp = list(zip(HP_files_Va, HP_labels_Va))
- random.shuffle(temp)
- HP_files_Va, HP_labels_Va = zip(*temp)
- temp = list(zip(BP_files, BP_labels))
- random.shuffle(temp)
- BP_files, BP_labels = zip(*temp)
- # %%
- tr_gen = create_dataset(HP_files_Tr, HP_labels_Tr, batch_size=256)
- va_gen = create_dataset(HP_files_Va, HP_labels_Va, batch_size=256)
- ts_gen = create_dataset(BP_files, BP_labels, batch_size=256)
- # %% [markdown]
- # # Model build
- # %%
- model = SupConModel(
- augmenter=keras.Sequential(
- [
- layers.Input(shape=(imsize, imsize, 2), name='AugInput'),
- layers.RandomTranslation(height_factor=(-0.1,0.1), width_factor=(-0.1,0.1), fill_mode='constant'),
- layers.RandomRotation((-45/360, 45/360), fill_mode='constant', name='AugRandRotate'),
- ],
- name="autmenter"),
- encoder=keras.Sequential(
- [
- layers.Input(shape=(imsize, imsize, 2), name='EncInput'),
- layers.Conv2D(width, kernel_size=kernal_size, strides=strides, activation="relu", name='EncCov2_1'),
- layers.Conv2D(width, kernel_size=kernal_size, strides=strides, activation="relu", name='EncCov2_2'),
- layers.Conv2D(width, kernel_size=kernal_size, strides=strides, activation="relu", name='EncCovFinal'),
- layers.GlobalAveragePooling2D(name='EncGAP'),
- ],
- name="encoder"),
- projection_head=keras.Sequential(
- [
- layers.Input(shape=(width,), name='ProjHeadInput'),
- layers.Dense(width, name='ProjHeadDense'),
- ],
- name="projection_head"),
- )
- # %%
- # Compile model with supervised contrastive loss
- model.compile(
- optimizer=keras.optimizers.Adam(learning_rate=encoder_lr),
- loss=supervised_contrastive_loss,
- metrics=[CorrelationAccuracyMetric(), CorrelationAccuracyNegMetric()]
- )
- # Print model summary
- model.summary()
- # %% [markdown]
- # # Training
- # %%
- class SaveEncoderCallback(tf.keras.callbacks.Callback):
- def __init__(self, encoder, filepath):
- super(SaveEncoderCallback, self).__init__()
- self.encoder = encoder
- self.filepath = filepath
- self.best_loss = float('inf')
- def on_epoch_end(self, epoch, logs=None):
- current_loss = logs.get('loss')
- if current_loss < self.best_loss:
- self.best_loss = current_loss
- self.encoder.save(self.filepath.format(epoch=epoch, loss=current_loss))
- mdl_Name = f'SupCon({imsize})'
- mdl_path = f'./{mdl_Name}/encoder.keras'
- if not os.path.exists(f'./{mdl_Name}/'):
- os.mkdir(f'./{mdl_Name}/')
- mcp = SaveEncoderCallback(model.encoder, mdl_path)
- # %%
- model.evaluate(tr_gen)
- # %%
- # run training
- history = model.fit(tr_gen, epochs=num_epochs_encoder, validation_data=va_gen, callbacks=[mcp])
- # %%
- model = tf.keras.models.load_model(mdl_path)
- model.add(layers.Lambda(lambda x: tf.nn.l2_normalize(x,axis=1), name='L2Norm'))
- print(f"Best val loss at epoah {np.argmin(history.history['val_loss'])}")
- # %% [markdown]
- # ---
- #
- # **Test set evaluation**
- # %%
- negCorr = CorrelationAccuracyNegMetric()
- posCorr = CorrelationAccuracyMetric()
- tsloss = []
- scoreTest = []
- labelsTsResult = []
- cnt = 0
- for batch in ts_gen:
- image, label = batch
- predTest = model.predict(image, verbose=False)
- negCorr.update_state(label, predTest)
- posCorr.update_state(label, predTest)
- tsloss.append(supervised_contrastive_loss(label, predTest).numpy())
- scoreTest.extend(predTest.tolist())
- labelsTsResult.extend(label.numpy().tolist())
- cnt += 1
- if cnt > 100:
- break
- print(f'ts_corr_neg_pair: {negCorr.result():.4f} - ts_corr_pos_pair: {posCorr.result():.4f} - ts_loss: {np.mean(tsloss)}')
- # %% [markdown]
- # # **Stage 2: Classifier**
- # %%
- def seed_everything(seed: int = 42):
- random.seed(seed)
- np.random.seed(seed)
- os.environ["PYTHONHASHSEED"] = str(seed)
- tf.random.set_seed(seed)
- seed_everything()
- # %% [markdown]
- # ## Load encoder
- # %%
- mdl_path = f'./{mdl_Name}/encoder.keras'
- encoder = tf.keras.models.load_model(mdl_path)
- # %% [markdown]
- # # Model build
- # %%
- class L2NormalizationLayer(layers.Layer):
- def __init__(self, **kwargs):
- super(L2NormalizationLayer, self).__init__(**kwargs)
- def call(self, inputs):
- return tf.math.l2_normalize(inputs, axis=1)
- def compute_output_shape(self, input_shape):
- return input_shape
- # %%
- encoder.trainable = encoder_trainable
- input = keras.Input((imsize, imsize, 2))
- x = layers.RandomRotation((-5/360, 5/360), fill_mode='constant', name='RandRotate')(input)
- for layer in encoder.layers:
- x = layer(x)
- x = L2NormalizationLayer(name='L2Norm')(x)
- output = layers.Dense(1, activation='sigmoid', name='Sigmoid')(x)
- myMdl = keras.Model(input, output)
- # %%
- myMdl.summary()
- # %%
- myMdl.compile(optimizer=keras.optimizers.Adam(learning_rate=classifier_lr),
- loss=keras.losses.BinaryCrossentropy(),
- metrics=[keras.metrics.AUC(name='AUC'),'accuracy']
- )
- # %%
- clfPath = f'./{mdl_Name}/classifier.keras'
- mcp = tf.keras.callbacks.ModelCheckpoint(filepath=clfPath,
- monitor='val_loss',
- save_best_only=True,
- mode='min')
- # %%
- # run training
- history2 = myMdl.fit(tr_gen, epochs=num_epochs_classifier, validation_data=va_gen, callbacks=[mcp])
- # %%
- custom_objects = {'L2NormalizationLayer': L2NormalizationLayer}
- myMdl = keras.models.load_model(clfPath, custom_objects=custom_objects, safe_mode=False)
- # %%
- myMdl.evaluate(ts_gen)
2.ModelImplementation.ipynb at commit 7c18ca6, no license · at the source
Overview
- Department of Electronics Engineering, Incheon National University,Incheon, South Korea
- Department of Neurology, Inje University Haeundae Paik Hospital,Busan, South Korea
- Department of Neurology, Chung-Ang University Gwangmyeong Hospital,Gwangmyeong, South Korea
- Department of Neurology, Uijeongbu Eulji Medical Center, Eulji University School of Medicine,Uijeongbu, South Korea
- Department of Neurology, Ulsan Hospital, Ulsan, South Korea
- Department of Neurology, Busan Paik Hospital, Inje University College of Medicine,Busan, South Korea
- Department of Neurology, Dong-A University College of Medicine,Busan, South Korea
- Department of Neurology and Stroke Center, Samsung Medical Center, Sungkyunkwan University School of Medicine,Seoul, South Korea
- Department of Neurology, Korea University Ansan Hospital,Ansan, South Korea
- Department of Electrical and Computer Engineering, Sungkyunkwan University,Suwon, South Korea
- Center for Neuroscience Imaging Research, Institute for Basic Science,Suwon, South Korea
- Department of Neurology, Hanyang University Guri Hospital, College of Medicine, Hanyang University,Guri, South Korea
Abstract
The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.
Repository
Its files are read in the Code ↔ Paper reader above, with 3 matches between paragraphs and lines of code.
Hwan-ho/SupConFLAIRChange
7c18ca6f79d829088615d6e66ec2bbfa38ce0f96, 14 April 2026Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
6 files
- 1.preprocessing.ipynb, Jupyter, 54 lines, 1 match
- 2.ModelImplementation.ip
ynb , Jupyter, 371 lines, 2 matches - CorrelationAccuracyMetri
c.py , Python, 37 lines - CorrelationAccuracyNegMe
tric.py , Python, 38 lines - supervised_contrastive_l
oss.py , Python, 12 lines - README.md, Text, 33 lines
Code availability statement
The paper has a code availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: Hwan-ho/
SupConFLAIRChange - it says that the code is available on request
Read it in the paper: doi.org/10.1038/s41746-026-02511-x.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 5 scripts, each with its path and the digest of its content;
- 3 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Code and data availability statement
The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:
- it points to the authors' code: Hwan-ho/
SupConFLAIRChange - it says that the data are available on request
- it says that the code is available on request
Read it in the paper: doi.org/10.1038/s41746-026-02511-x.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 30 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 4 keywords, 5 funders, 20 references.
Cite
This paper
Cho, H.-h., Lee, J., Bae, J., Lee, D., Kim, H. C., Lee, S. Y., Seo, J. H., Seo, W.-K., Jung, J.-M., Park, H., & Park, S. (2026). Automated detection of new cerebral infarctions and prognostic implications using deep learning on serial MRI. NPJ digital medicine, 9(1), 316. https://
BibTeX
@article{cho2026automate
author = {Cho, Hwan-ho and Lee, Joonwon and Bae, Jeonghoon and Lee, Dongwhane and Kim, Hyung Chan and Lee, Suk Yoon and Seo, Jung Hwa and Seo, Woo-Keun and Jung, Jin-Man and Park, Hyunjin and Park, Seongho},
title = {{Automated detection of new cerebral infarctions and prognostic implications using deep learning on serial MRI}},
journal = {NPJ digital medicine},
year = {2026},
month = mar,
volume = {9},
number = {1},
pages = {316},
publisher = {Nature Publishing Group},
issn = {2398-6352},
doi = {10.1038/
url = {https://
pmid = {41786919},
pmcid = {PMC13079727}
}
RIS
TY - JOUR
AU - Cho, Hwan-ho
AU - Lee, Joonwon
AU - Bae, Jeonghoon
AU - Lee, Dongwhane
AU - Kim, Hyung Chan
AU - Lee, Suk Yoon
AU - Seo, Jung Hwa
AU - Seo, Woo-Keun
AU - Jung, Jin-Man
AU - Park, Hyunjin
AU - Park, Seongho
TI - Automated detection of new cerebral infarctions and prognostic implications using deep learning on serial MRI
T2 - NPJ digital medicine
J2 - NPJ Digit Med
PY - 2026
DA - 2026/
VL - 9
IS - 1
SP - 316
SN - 2398-6352
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Automated detection of new cerebral infarctions and prognostic implications using deep learning on serial MRI",
"container-title": "NPJ digital medicine",
"author": [
{
"family": "Cho",
"given": "Hwan-ho"
},
{
"family": "Lee",
"given": "Joonwon"
},
{
"family": "Bae",
"given": "Jeonghoon"
},
{
"family": "Lee",
"given": "Dongwhane"
},
{
"family": "Kim",
"given": "Hyung Chan"
},
{
"family": "Lee",
"given": "Suk Yoon"
},
{
"family": "Seo",
"given": "Jung Hwa"
},
{
"family": "Seo",
"given": "Woo-Keun"
},
{
"family": "Jung",
"given": "Jin-Man"
},
{
"family": "Park",
"given": "Hyunjin"
},
{
"family": "Park",
"given": "Seongho"
}
],
"container-title-short":
"volume": "9",
"issue": "1",
"page": "316",
"DOI": "10.1038/
"PMID": "41786919",
"PMCID": "PMC13079727",
"ISSN": "2398-6352",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
3,
5
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1002/hbm.70602 [code]
- Neuroimaging Correlates of Post-Stroke Pain After Ischemic Stroke: Secondary Analysis of the INSPiRE-TMS Trial.Journal: Human brain mappingIn common: Keras, ANTs, TensorFlow, 1 other tool, stroke, clinical / translational
- [2] doi:10.1111/ene.70678 [code]
- Who Falls After a Stroke? Evidence From a Prospective Stroke Cohort.Journal: European journal of neurologyIn common: Keras, ANTs, TensorFlow, 1 other tool, stroke, clinical / translational
- [3] doi:10.1002/epi.70296 [code]
- Fully automated three-dimensional deep learning-based magnetic resonance imaging segmentation of brain cavities in epilepsy surgery.Journal: EpilepsiaIn common: Keras, ANTs, TensorFlow, 1 other tool, structural MRI / diffusion, clinical / translational
- [4] doi:10.1186/s12938-026-01555-0 [code]
- Incorporating normal periventricular changes for enhanced pathological white matter hyperintensity segmentation: on multiclass deep learning approaches.Journal: Biomedical engineering onlineIn common: Keras, TensorFlow, NumPy, stroke, structural MRI / diffusion, 1 reference
- [5] doi:10.1038/s41598-026-47047-y [code]
- Imaging-genetics-based dementia risk prediction using deep survival neural networks in the Rotterdam Study.Journal: Scientific reportsIn common: Keras, TensorFlow, NumPy, structural MRI / diffusion, clinical / translational, 1 reference
- [6] doi:10.1162/imag.a.1366 [code]
- MICAFlow: Fast and robust MRI preprocessing bridging research neuroimaging and clinical practice.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Keras, ANTs, TensorFlow, 1 other tool, structural MRI / diffusion
- [7] doi:10.1162/imag.a.1164 [code]
- Bias and generalizability of brain age prediction models: A multi-cohort evaluation with anatomical and interpretability insights.Journal: Imaging neuroscience (Cambridge, Mass.)In common: Keras, ANTs, TensorFlow, 1 other tool, structural MRI / diffusion
- [8] doi:10.1038/s41586-026-10631-3 [code]
- A prognostic human brain network for diffuse midline glioma.Journal: NatureIn common: Keras, ANTs, TensorFlow, 1 other tool, clinical / translational
- [9] doi:10.1002/ana.78206 [code]
- Multimodal Image Guidance in Subthalamic Deep Brain Stimulation for Parkinson's Disease.Journal: Annals of neurologyIn common: Keras, ANTs, TensorFlow, 1 other tool, clinical / translational
- [10] doi:10.1038/s41467-026-71555-0 [code]
- A deep representation learning model to predict response to vagus nerve stimulation.Journal: Nature communicationsIn common: ANTs, NumPy, structural MRI / diffusion, clinical / translational, 2 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 5 scripts, and 3 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:30762205bd5fee00…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
