{
  "id": 695702,
  "title": "1st Place Solution",
  "url": "/competitions/recodai-luc-scientific-image-forgery-detection/writeups/1st-place-solution",
  "author_name": "",
  "post_date": "2026-04-30T06:32:15.007Z",
  "votes": 37,
  "comment_count": 5,
  "views": 0,
  "content": "<h1>Brief Description of the Solution</h1>\n<p>The final solution followed the pipeline below:</p>\n<ol>\n<li>Detect relevant image regions: <strong>Blot</strong> (western blot) and <strong>Microscopy</strong> (microscopy/macroscopy) using YOLO-based panel detectors.</li>\n<li>Retrieve suspicious candidate pairs using embedding models trained separately for each data type.</li>\n<li>Localize duplicated or overlapping regions using keypoint matching and geometric verification.</li>\n<li>If no matches were found, fall back to the best available public Kaggle segmentation kernel, constrained by Blot/Microscopy masks. I initially used <a href=\"https://www.kaggle.com/ravaghi\" target=\"_blank\">@ravaghi</a>’s version and later replaced it with <a href=\"https://www.kaggle.com/pankajiitr\" target=\"_blank\">@pankajiitr</a>’s code without modifications. This consistently provided a ~0.005–0.01 improvement, so I included it in both final submissions.</li>\n<li>If no evidence was detected, classify the sample as <strong>authentic</strong>.</li>\n</ol>\n<h2><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fba1a1b08fe068b52f212b728270fe808%2F2026-04-29%20183252.png?generation=1777473283918756&amp;alt=media\" alt=\"\"></h2>\n<h1>Code</h1>\n<p>I packaged the solution as a Python library and published the model weights through a GitHub Release.</p>\n<p>Check out the <a href=\"https://github.com/vlad3996/forgeryscope\" target=\"_blank\">vlad3996/forgeryscope repository</a> for installation instructions, examples, and pretrained weights.</p>\n<p>A single-image inference example is available in <a href=\"https://github.com/vlad3996/forgeryscope/blob/main/examples/quick_start.ipynb\" target=\"_blank\">examples/quick_start.ipynb</a>, and a simplified Kaggle submission pipeline is available <a href=\"https://www.kaggle.com/code/orshanec/1st-place-solution\" target=\"_blank\">here</a>.</p>\n<p>The package includes two YOLO-based detection models, four embedding models, and two keypoint extraction and matching pipelines. The core pipeline logic remains unchanged, with simplifications applied mainly to reduce complexity and improve usability.</p>\n<p>Compared to my <a href=\"https://www.kaggle.com/code/orshanec/1st-place-solution-0-550-private-0-450-public\" target=\"_blank\">best competition solution</a>, this simplified release:</p>\n<ul>\n<li>excludes the public segmentation kernel</li>\n<li>uses a single microscopy embedding model instead of an ensemble of three</li>\n<li>relies on the best-performing single panel detector rather than an ensemble of two YOLO detectors</li>\n</ul>\n<hr>\n<h1>Data</h1>\n<p>I started by reviewing related publications to better understand the domain and build intuition about the data. This analysis suggested that roughly 50% of reported cases involve western blots, while microscopy and macroscopy images make up a substantial portion of the remainder. For simplicity, I merged microscopy and macroscopy into a single class. Based on this, I focused on two categories (western blots and microscopy/macroscopy images) and deprioritized the rest, as they were less likely to be prominent in the test set. I also experimented with a simple detector for identical <a href=\"https://pubpeer.com/publications/739636EFE147869FB290A67B01E5C0\" target=\"_blank\">XRD patterns</a>, but it had no measurable impact on the leaderboard, so I abandoned this direction. Similarly, I chose not to invest time in inpainting detection, as it appears relatively rare in real publications and I expected an existing public kernel to handle such cases with some degree of success if needed; the same reasoning applied to microscopy images with duplicated cells.</p>\n<p>In parallel, I set up a pipeline to download biomedical papers from <a href=\"https://pmc.ncbi.nlm.nih.gov/\" target=\"_blank\">PubMed Central</a>. By mid-December, I had collected ~2 million .tar.gz archives (~14 TB) with licenses such as CC0 and CC BY. I extracted source images from the PDFs, applied YOLO-based panel detection, and obtained around 6.6M western blot images and 16.3M microscopy/macroscopy images. I then progressively built a deduplicated dataset, aiming to keep only one image per visual identity. In practice, exact duplicates are relatively uncommon in biomedical figures; however, I still identified hundreds of thousands of near-duplicate or even identical samples. Some of these cases have also been reported on <a href=\"https://pubpeer.com/\" target=\"_blank\">PubPeer</a>, although many likely remained unnoticed. This dataset later proved useful for training embedding models.</p>\n<p>In addition to the large training dataset, I created a reliable validation set. It was initially based on BioFors, but I found it insufficient for proper evaluation: many negative pairs were actually positive pairs, and some positive pairs were mislabeled. To address this, I reannotated all positive pairs and as many negative pairs as possible. This became one of the most important parts of the pipeline: without a clean validation set, it was nearly impossible to make reliable progress.</p>\n<hr>\n<h1>Baseline</h1>\n<p>At the beginning of the competition, I established the initial pipeline structure, which remained largely unchanged throughout. It consisted of the organizers’ YOLO-based panel extractor, followed by candidate filtering using SigLIP2 for microscopy and a binary “similar / non-similar” classifier for western blots. The selected candidates were then passed to a LightGlue-SuperPoint matcher for precise localization (<em>max_keypoints = 2048, inlier_threshold = 100, match_score_threshold = 0.6</em>). This setup achieved a score of 0.327. When ensembled with a 0.319 <a href=\"https://www.kaggle.com/code/ravaghi/scientific-image-forgery-detection-dinov2\" target=\"_blank\">public kernel</a>, it reached <strong>0.341</strong>, which was the top score at that stage (with the second-best at 0.324).</p>\n<p>Later, I improved the pipeline to 0.342 by replacing SigLIP2 with my binary classifier and retraining YOLO on my own data. Combined with the public kernel, this version reached approximately <strong>0.353</strong>. At that point, the best score was ~0.355 by <a href=\"https://www.kaggle.com/melgor\" target=\"_blank\">@melgor</a>, and his presence on the leaderboard motivated me to invest more effort into embedding-based approaches. I also began to observe performance saturation, so I gradually replaced the classification models with embedding models and focused on embeddings for the rest of the competition.</p>\n<p>Rough progression: </p>\n<table>\n<thead>\n<tr>\n<th>Stage</th>\n<th>Public LB score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Initial pipeline</td>\n<td>0.327</td>\n</tr>\n<tr>\n<td>+ public DINOv2 kernel</td>\n<td>0.341</td>\n</tr>\n<tr>\n<td>+ SigLIP2 -&gt; custom classifier and retrained YOLO</td>\n<td>0.353</td>\n</tr>\n<tr>\n<td>+ western blot embeddings</td>\n<td>0.370</td>\n</tr>\n<tr>\n<td>+ SuperPoint -&gt; pretrained SIFT and ALIKED</td>\n<td>0.375</td>\n</tr>\n<tr>\n<td>+ lane-level western blot matching</td>\n<td>0.431</td>\n</tr>\n<tr>\n<td>+ stronger microscopy embedder / retrained ALIKED / TTA / ensembling / threshold tuning</td>\n<td>0.456–0.458</td>\n</tr>\n</tbody>\n</table>\n<p>Note: values are approximate and based on 250+ submissions; improvements are not strictly incremental.</p>\n<p>One additional note: I experimented with adding a body imaging class, similar to microscopy/macroscopy. On the public leaderboard, this provided a small boost (from 0.456 to 0.458), so I included it in one of the two final submissions. However, on the private leaderboard, it performed worse than the 0.450 submission that did not include body imaging panels:\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb6af6b886ff3a17179ef2e715522f1e1%2F2026-04-28%20230125.png?generation=1777402911753014&amp;alt=media\" alt=\"\"></p>\n<hr>\n<h1>YOLO-based Panel Detector</h1>\n<p>As a starting point for the detector, I used the competition organizers’ <a href=\"https://github.com/phillipecardenuto/upm/tree/main/panel-extractor\" target=\"_blank\">detector</a>. However, I quickly encountered several issues, including <strong>missed detections</strong>, <strong>false positives</strong>, and <strong>inconsistent labels</strong>.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F3917a8436b5c13528e6548a412459a9c%2F2026-04-27%20170820.png?generation=1777296062816155&amp;alt=media\" alt=\"\"></p>\n<p>To address this, I reannotated the dataset to correct inaccurate bounding boxes and labeling errors, and to better align it with the competition requirements (e.g., handling single-panel images). I also sampled images from a large unlabeled biomedical dataset and annotated them. In addition, I analyzed failure cases of the detector and incorporated those examples into the training set.</p>\n<p>In total, this resulted in 11.3k training figures in version 9 of the dataset and 12.3k in version 10. However, leaderboard performance with version 10 was slightly worse, so I stopped further annotation at that point.</p>\n<p>The model was trained using the Ultralytics YOLO library with the following command:</p>\n<p><code>yolo detect train   model=yolo12x.pt   data=dataset_V9.yaml  epochs=100   imgsz=640   batch=80   translate=0.2  flipud=0.2 mosaic=1 cos_lr=True warmup_epochs=0 optimizer=\"radam\" lr0=0.001</code></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F477661ee5ca5c6f2db79fe84d9e1475d%2F2026-04-27%20173634.png?generation=1777297044745513&amp;alt=media\" alt=\"\"></p>\n<p>Panels corresponding to {\"Graphs\", \"Flow Cytometry\", \"Body Imaging\"} were excluded in the best private submission.</p>\n<p>Towards the end of the competition, I trained a multiscale variant and used an ensemble of these models for the final submission, which provided a +0.002–0.003 improvement on the leaderboard compared to the single model above:</p>\n<p><code>yolo detect train   model=yolo12x.pt   data=dataset_V10.yaml  epochs=80   imgsz=640   batch=16   translate=0.2  flipud=0.2 mosaic=1 multi_scale=True</code></p>\n<hr>\n<h1>Embeddings</h1>\n<p>Midway through the competition, I shifted my focus to learning embeddings that maximize separation between similar and dissimilar samples. In this context, I treated two types of relationships separately:</p>\n<ul>\n<li><strong>Duplicate</strong> — images that are approximately 80–100% derived from the same source.</li>\n<li><strong>Overlap</strong> — images that share roughly 5–100% common origin (with duplicates being a subset of this category).</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fd6cd9c1c639121bf64eefd1eb9695445%2F2026-04-27%20215210.png?generation=1777312401814733&amp;alt=media\" alt=\"\"></p>\n<p>I used deduplicated datasets for western blot and microscopy/macroscopy images, investing significant effort to ensure high data quality, under the assumption that each image appears only once. I started with <a href=\"https://arxiv.org/pdf/1807.03748\" target=\"_blank\">InfoNCE loss</a>, but then switched to <a href=\"https://arxiv.org/pdf/2004.11362\" target=\"_blank\">SupCon</a>. For each batch, I sampled N images and generated one augmented version per image, resulting in 2N samples. This produced N positive (similar) pairs, while all remaining pairs within the batch were treated as non-similar. I experimented with various alternative metric learning losses, but they consistently underperformed, so I ultimately adopted <code>SupCon</code> as the primary training objective, using a temperature between 0.07 and 0.09 and the largest batch size permitted by GPU memory. I used <a href=\"https://huggingface.co/timm/nextvit_small.bd_ssld_6m_in1k\" target=\"_blank\">nextvit_small</a> as the backbone for each model toward the end of the competition. </p>\n<p>For western blot “Duplicate” cases, I applied augmentations inspired by real-world variations. I used the <a href=\"https://github.com/albumentations-team/albumentations\" target=\"_blank\">albumentations</a> library for the following transformations:</p>\n<ul>\n<li>geometric transformations: shifts, scaling, small rotations, perspective transforms, and grid distortions</li>\n<li>flips and rotations: horizontal/vertical flips and 180° rotation</li>\n<li>color transformations: inversion, CLAHE, and grayscale conversion</li>\n<li>image quality degradation: blur and compression</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F4e975dfb57582f524f7943e6c3ccda27%2F2026-04-27%20222419.png?generation=1777314283147218&amp;alt=media\" alt=\"\"></p>\n<p>For the western blot “Overlap” case, I initially relied on simple heuristics, but later replaced them with bounding boxes from a YOLO-based lane detector. The goal was to identify two views of an image that share at least one common black “lane.” This could correspond either to a simple overlap or to a “zoom” scenario, where one image is nested within another. The cropping process was then restricted to the green-highlighted regions shown below, corresponding to the gaps between black lanes in the western blot image:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fefe2cfa0c7604b4cf3e14fee65ce2a07%2F2026-04-27%20220956.png?generation=1777313425171714&amp;alt=media\" alt=\"\"></p>\n<p>For microscopy/macroscopy images, I designed custom augmentations to simulate common artifacts observed in real data. These included adding “noisy” elements such as letters, bounding boxes, zoom callouts, arrows, and lines, along with CoarseDropout (inspired by real inpainting and cell copy-paste examples that my model struggled to handle correctly). Since not all images were suitable for generating “overlap” or “zoom” crops, I clustered the dataset and manually labeled the clusters into two categories: suitable for cropping and not suitable. \n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F40b20a9c68f077fffaf41823e94f00af%2F2026-04-27%20225153.png?generation=1777315934812421&amp;alt=media\" alt=\"\"></p>\n<p>Switching from classification to embedding models for western blots improved the score by +0.017: from 0.353 to <strong>0.370</strong>. Further gains came from improving the western blot overlap embedder by incorporating augmentations based on bounding boxes from the lane detector described below, adding approximately +0.010. The microscopy embedder was more challenging to train due to higher input resolution and the larger dataset size, so I used a cloud machine (A100 or RTX 6000) instead of my local RTX 4090 setup. This provided an additional boost toward the end of the competition, improving the score by +0.015 (from 0.431 to 0.446).</p>\n<hr>\n<h1>The Key Breakthrough: Matching Individual Western Blot Lanes</h1>\n<p>By mid-December, I decided to commit to a time-consuming idea that I believed could reveal duplication patterns missed by panel-level matching: while western blot images are generally unique, each individual lane within a blot should be unique as well. Without clear evidence that this would work, I spent about a week manually annotating bounding boxes for lane detection. In total, I labeled 3,732 images to train a YOLO-based detector for dark regions in western blot images. I then applied this model to the full dataset of ~6M blots, producing approximately 27M bounding boxes:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fcdcfd828a7dad58c9fad610d652f6076%2F2026-04-27%20232637.png?generation=1777318021984840&amp;alt=media\" alt=\"\"></p>\n<p>I reused the pipeline from the western blot “Duplicate” case, modifying only the input resolution and the strength of augmentations. The first checkpoint improved the leaderboard score from 0.375 to 0.397 without any threshold tuning, and further adjustment of the similarity threshold increased it to 0.401 — and that is how I ended up spending New Year’s Eve.</p>\n<p>After cleaning the dataset and tuning the training hyperparameters, I reached a score of <strong>0.416</strong>. At this point, the model started detecting cases like these:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F9bf008b292e573f701ea75201b213a88%2F2026-04-27%20234225.png?generation=1777318975369083&amp;alt=media\" alt=\"\"></p>\n<p>Finally, applying a simple heuristic provided another boost to <strong>0.431</strong>:</p>\n<blockquote>\n  <p>Count the number of lanes in each panel involved in matches.\n  If more than 50% of lanes in a panel are matched, use the entire panel bounding box in the mask instead of individual segment bounding boxes.</p>\n</blockquote>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F881b367125632bfa5a78e326060e6651%2F2026-04-27%20234754.png?generation=1777319300391967&amp;alt=media\" alt=\"\"></p>\n<p>This lane-level component was the main driver of the <strong>jump from 0.375 to 0.431</strong>. The improvement from 0.431 to 0.458 was primarily driven by a stronger microscopy embedding model. Additional gains came from applying TTA, light ensembling, and tuning the inference thresholds for similarity and inliers.</p>\n<hr>\n<h1>Matching</h1>\n<p>Candidates from the embedding stage were passed to a matching module, which filtered out non-relevant microscopy pairs using inlier count and inlier score thresholds. For western blots, all candidates were retained for mask generation, even when no keypoints were matched.</p>\n<p>For microscopy images, SIFT performed surprisingly well, and despite multiple attempts, I was unable to replace it with more modern alternatives. However, SIFT struggled on western blots, often failing to extract reliable keypoints. To address this, I trained a neural matching pipeline using <a href=\"https://github.com/cvg/glue-factory\" target=\"_blank\">Glue Factory</a> (ALIKED-n16 + LightGlue). Compared with SIFT-based matching for western blots, this significantly accelerated inference (from ~2.5 hours to ~20 minutes), while providing only marginal improvement in leaderboard score (+0.005 or less).</p>\n<p><strong>Final setup</strong>:</p>\n<ul>\n<li>Microscopy: LightGlue with SIFT features and RANSAC-based geometric verification</li>\n<li>Western blots: LightGlue with ALIKED features and MAGSAC-based geometric verification</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb93678952e53b2ad7e71305b00f4984a%2F2026-04-28%20144014.png?generation=1777372849011274&amp;alt=media\" alt=\"\"></p>\n<hr>\n<h1>What Mattered Most</h1>\n<p>The most important factors were:</p>\n<ul>\n<li>Building a clean validation set instead of relying directly on noisy public data.</li>\n<li>Training separate models for western blots and microscopy/macroscopy images, with domain-specific augmentations.</li>\n<li>Moving from classification to embedding-based retrieval.</li>\n<li>Matching individual western blot lanes rather than only full panels.</li>\n<li>Using domain-specific augmentations that reflected real failure cases.</li>\n</ul>\n<hr>\n<h1>Final Thoughts</h1>\n<p>I would like to thank the organizers for providing a real-world test dataset and for hosting the competition on Kaggle, which made it easy to iterate and compare approaches with others.</p>\n<p>This competition reshaped my perspective on scientific image integrity. Before the competition, I assumed that duplication or manipulation in biomedical research was relatively rare. However, as I explored the literature and worked with the data, it became clear that such cases are more prevalent than I initially expected, and that many subtle cases may remain undiscovered.</p>\n<p>One of the clearest indicators of this complexity was the performance gap: improving from the organizers' solution (~0.35 according to the recent post) to the top private leaderboard score (0.55) reflects a substantial gain in detection capability. My evaluation of existing open-source tools (e.g. BusterNet) also suggested that many academic methods do not transfer well to this setting.</p>\n<p>At the same time, I am cautious about how such models should be used in practice. During this work, I encountered many cases that appeared \"<em>more similar than would be expected by chance</em>\", but I would not draw conclusions about the underlying reasons without careful expert analysis. Tools like this should be treated as decision-support systems rather than definitive evidence.</p>\n<p>Finally, I believe that making strong solutions more accessible can have meaningful practical impact. Providing tools that editors and researchers can use in real editorial workflows may help bridge the gap between research and practice.</p>",
  "messages": [
    {
      "id": "3450647",
      "postDate": "04/30/2026 06:30:28",
      "content": "<h1>Brief Description of the Solution</h1>\n<p>The final solution followed the pipeline below:</p>\n<ol>\n<li>Detect relevant image regions: <strong>Blot</strong> (western blot) and <strong>Microscopy</strong> (microscopy/macroscopy) using YOLO-based panel detectors.</li>\n<li>Retrieve suspicious candidate pairs using embedding models trained separately for each data type.</li>\n<li>Localize duplicated or overlapping regions using keypoint matching and geometric verification.</li>\n<li>If no matches were found, fall back to the best available public Kaggle segmentation kernel, constrained by Blot/Microscopy masks. I initially used <a href=\"https://www.kaggle.com/ravaghi\" target=\"_blank\">@ravaghi</a>’s version and later replaced it with <a href=\"https://www.kaggle.com/pankajiitr\" target=\"_blank\">@pankajiitr</a>’s code without modifications. This consistently provided a ~0.005–0.01 improvement, so I included it in both final submissions.</li>\n<li>If no evidence was detected, classify the sample as <strong>authentic</strong>.</li>\n</ol>\n<h2><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fba1a1b08fe068b52f212b728270fe808%2F2026-04-29%20183252.png?generation=1777473283918756&amp;alt=media\" alt=\"\"></h2>\n<h1>Code</h1>\n<p>I packaged the solution as a Python library and published the model weights through a GitHub Release.</p>\n<p>Check out the <a href=\"https://github.com/vlad3996/forgeryscope\" target=\"_blank\">vlad3996/forgeryscope repository</a> for installation instructions, examples, and pretrained weights.</p>\n<p>A single-image inference example is available in <a href=\"https://github.com/vlad3996/forgeryscope/blob/main/examples/quick_start.ipynb\" target=\"_blank\">examples/quick_start.ipynb</a>, and a simplified Kaggle submission pipeline is available <a href=\"https://www.kaggle.com/code/orshanec/1st-place-solution\" target=\"_blank\">here</a>.</p>\n<p>The package includes two YOLO-based detection models, four embedding models, and two keypoint extraction and matching pipelines. The core pipeline logic remains unchanged, with simplifications applied mainly to reduce complexity and improve usability.</p>\n<p>Compared to my <a href=\"https://www.kaggle.com/code/orshanec/1st-place-solution-0-550-private-0-450-public\" target=\"_blank\">best competition solution</a>, this simplified release:</p>\n<ul>\n<li>excludes the public segmentation kernel</li>\n<li>uses a single microscopy embedding model instead of an ensemble of three</li>\n<li>relies on the best-performing single panel detector rather than an ensemble of two YOLO detectors</li>\n</ul>\n<hr>\n<h1>Data</h1>\n<p>I started by reviewing related publications to better understand the domain and build intuition about the data. This analysis suggested that roughly 50% of reported cases involve western blots, while microscopy and macroscopy images make up a substantial portion of the remainder. For simplicity, I merged microscopy and macroscopy into a single class. Based on this, I focused on two categories (western blots and microscopy/macroscopy images) and deprioritized the rest, as they were less likely to be prominent in the test set. I also experimented with a simple detector for identical <a href=\"https://pubpeer.com/publications/739636EFE147869FB290A67B01E5C0\" target=\"_blank\">XRD patterns</a>, but it had no measurable impact on the leaderboard, so I abandoned this direction. Similarly, I chose not to invest time in inpainting detection, as it appears relatively rare in real publications and I expected an existing public kernel to handle such cases with some degree of success if needed; the same reasoning applied to microscopy images with duplicated cells.</p>\n<p>In parallel, I set up a pipeline to download biomedical papers from <a href=\"https://pmc.ncbi.nlm.nih.gov/\" target=\"_blank\">PubMed Central</a>. By mid-December, I had collected ~2 million .tar.gz archives (~14 TB) with licenses such as CC0 and CC BY. I extracted source images from the PDFs, applied YOLO-based panel detection, and obtained around 6.6M western blot images and 16.3M microscopy/macroscopy images. I then progressively built a deduplicated dataset, aiming to keep only one image per visual identity. In practice, exact duplicates are relatively uncommon in biomedical figures; however, I still identified hundreds of thousands of near-duplicate or even identical samples. Some of these cases have also been reported on <a href=\"https://pubpeer.com/\" target=\"_blank\">PubPeer</a>, although many likely remained unnoticed. This dataset later proved useful for training embedding models.</p>\n<p>In addition to the large training dataset, I created a reliable validation set. It was initially based on BioFors, but I found it insufficient for proper evaluation: many negative pairs were actually positive pairs, and some positive pairs were mislabeled. To address this, I reannotated all positive pairs and as many negative pairs as possible. This became one of the most important parts of the pipeline: without a clean validation set, it was nearly impossible to make reliable progress.</p>\n<hr>\n<h1>Baseline</h1>\n<p>At the beginning of the competition, I established the initial pipeline structure, which remained largely unchanged throughout. It consisted of the organizers’ YOLO-based panel extractor, followed by candidate filtering using SigLIP2 for microscopy and a binary “similar / non-similar” classifier for western blots. The selected candidates were then passed to a LightGlue-SuperPoint matcher for precise localization (<em>max_keypoints = 2048, inlier_threshold = 100, match_score_threshold = 0.6</em>). This setup achieved a score of 0.327. When ensembled with a 0.319 <a href=\"https://www.kaggle.com/code/ravaghi/scientific-image-forgery-detection-dinov2\" target=\"_blank\">public kernel</a>, it reached <strong>0.341</strong>, which was the top score at that stage (with the second-best at 0.324).</p>\n<p>Later, I improved the pipeline to 0.342 by replacing SigLIP2 with my binary classifier and retraining YOLO on my own data. Combined with the public kernel, this version reached approximately <strong>0.353</strong>. At that point, the best score was ~0.355 by <a href=\"https://www.kaggle.com/melgor\" target=\"_blank\">@melgor</a>, and his presence on the leaderboard motivated me to invest more effort into embedding-based approaches. I also began to observe performance saturation, so I gradually replaced the classification models with embedding models and focused on embeddings for the rest of the competition.</p>\n<p>Rough progression: </p>\n<table>\n<thead>\n<tr>\n<th>Stage</th>\n<th>Public LB score</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Initial pipeline</td>\n<td>0.327</td>\n</tr>\n<tr>\n<td>+ public DINOv2 kernel</td>\n<td>0.341</td>\n</tr>\n<tr>\n<td>+ SigLIP2 -&gt; custom classifier and retrained YOLO</td>\n<td>0.353</td>\n</tr>\n<tr>\n<td>+ western blot embeddings</td>\n<td>0.370</td>\n</tr>\n<tr>\n<td>+ SuperPoint -&gt; pretrained SIFT and ALIKED</td>\n<td>0.375</td>\n</tr>\n<tr>\n<td>+ lane-level western blot matching</td>\n<td>0.431</td>\n</tr>\n<tr>\n<td>+ stronger microscopy embedder / retrained ALIKED / TTA / ensembling / threshold tuning</td>\n<td>0.456–0.458</td>\n</tr>\n</tbody>\n</table>\n<p>Note: values are approximate and based on 250+ submissions; improvements are not strictly incremental.</p>\n<p>One additional note: I experimented with adding a body imaging class, similar to microscopy/macroscopy. On the public leaderboard, this provided a small boost (from 0.456 to 0.458), so I included it in one of the two final submissions. However, on the private leaderboard, it performed worse than the 0.450 submission that did not include body imaging panels:\n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb6af6b886ff3a17179ef2e715522f1e1%2F2026-04-28%20230125.png?generation=1777402911753014&amp;alt=media\" alt=\"\"></p>\n<hr>\n<h1>YOLO-based Panel Detector</h1>\n<p>As a starting point for the detector, I used the competition organizers’ <a href=\"https://github.com/phillipecardenuto/upm/tree/main/panel-extractor\" target=\"_blank\">detector</a>. However, I quickly encountered several issues, including <strong>missed detections</strong>, <strong>false positives</strong>, and <strong>inconsistent labels</strong>.</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F3917a8436b5c13528e6548a412459a9c%2F2026-04-27%20170820.png?generation=1777296062816155&amp;alt=media\" alt=\"\"></p>\n<p>To address this, I reannotated the dataset to correct inaccurate bounding boxes and labeling errors, and to better align it with the competition requirements (e.g., handling single-panel images). I also sampled images from a large unlabeled biomedical dataset and annotated them. In addition, I analyzed failure cases of the detector and incorporated those examples into the training set.</p>\n<p>In total, this resulted in 11.3k training figures in version 9 of the dataset and 12.3k in version 10. However, leaderboard performance with version 10 was slightly worse, so I stopped further annotation at that point.</p>\n<p>The model was trained using the Ultralytics YOLO library with the following command:</p>\n<p><code>yolo detect train   model=yolo12x.pt   data=dataset_V9.yaml  epochs=100   imgsz=640   batch=80   translate=0.2  flipud=0.2 mosaic=1 cos_lr=True warmup_epochs=0 optimizer=\"radam\" lr0=0.001</code></p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F477661ee5ca5c6f2db79fe84d9e1475d%2F2026-04-27%20173634.png?generation=1777297044745513&amp;alt=media\" alt=\"\"></p>\n<p>Panels corresponding to {\"Graphs\", \"Flow Cytometry\", \"Body Imaging\"} were excluded in the best private submission.</p>\n<p>Towards the end of the competition, I trained a multiscale variant and used an ensemble of these models for the final submission, which provided a +0.002–0.003 improvement on the leaderboard compared to the single model above:</p>\n<p><code>yolo detect train   model=yolo12x.pt   data=dataset_V10.yaml  epochs=80   imgsz=640   batch=16   translate=0.2  flipud=0.2 mosaic=1 multi_scale=True</code></p>\n<hr>\n<h1>Embeddings</h1>\n<p>Midway through the competition, I shifted my focus to learning embeddings that maximize separation between similar and dissimilar samples. In this context, I treated two types of relationships separately:</p>\n<ul>\n<li><strong>Duplicate</strong> — images that are approximately 80–100% derived from the same source.</li>\n<li><strong>Overlap</strong> — images that share roughly 5–100% common origin (with duplicates being a subset of this category).</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fd6cd9c1c639121bf64eefd1eb9695445%2F2026-04-27%20215210.png?generation=1777312401814733&amp;alt=media\" alt=\"\"></p>\n<p>I used deduplicated datasets for western blot and microscopy/macroscopy images, investing significant effort to ensure high data quality, under the assumption that each image appears only once. I started with <a href=\"https://arxiv.org/pdf/1807.03748\" target=\"_blank\">InfoNCE loss</a>, but then switched to <a href=\"https://arxiv.org/pdf/2004.11362\" target=\"_blank\">SupCon</a>. For each batch, I sampled N images and generated one augmented version per image, resulting in 2N samples. This produced N positive (similar) pairs, while all remaining pairs within the batch were treated as non-similar. I experimented with various alternative metric learning losses, but they consistently underperformed, so I ultimately adopted <code>SupCon</code> as the primary training objective, using a temperature between 0.07 and 0.09 and the largest batch size permitted by GPU memory. I used <a href=\"https://huggingface.co/timm/nextvit_small.bd_ssld_6m_in1k\" target=\"_blank\">nextvit_small</a> as the backbone for each model toward the end of the competition. </p>\n<p>For western blot “Duplicate” cases, I applied augmentations inspired by real-world variations. I used the <a href=\"https://github.com/albumentations-team/albumentations\" target=\"_blank\">albumentations</a> library for the following transformations:</p>\n<ul>\n<li>geometric transformations: shifts, scaling, small rotations, perspective transforms, and grid distortions</li>\n<li>flips and rotations: horizontal/vertical flips and 180° rotation</li>\n<li>color transformations: inversion, CLAHE, and grayscale conversion</li>\n<li>image quality degradation: blur and compression</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F4e975dfb57582f524f7943e6c3ccda27%2F2026-04-27%20222419.png?generation=1777314283147218&amp;alt=media\" alt=\"\"></p>\n<p>For the western blot “Overlap” case, I initially relied on simple heuristics, but later replaced them with bounding boxes from a YOLO-based lane detector. The goal was to identify two views of an image that share at least one common black “lane.” This could correspond either to a simple overlap or to a “zoom” scenario, where one image is nested within another. The cropping process was then restricted to the green-highlighted regions shown below, corresponding to the gaps between black lanes in the western blot image:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fefe2cfa0c7604b4cf3e14fee65ce2a07%2F2026-04-27%20220956.png?generation=1777313425171714&amp;alt=media\" alt=\"\"></p>\n<p>For microscopy/macroscopy images, I designed custom augmentations to simulate common artifacts observed in real data. These included adding “noisy” elements such as letters, bounding boxes, zoom callouts, arrows, and lines, along with CoarseDropout (inspired by real inpainting and cell copy-paste examples that my model struggled to handle correctly). Since not all images were suitable for generating “overlap” or “zoom” crops, I clustered the dataset and manually labeled the clusters into two categories: suitable for cropping and not suitable. \n<img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F40b20a9c68f077fffaf41823e94f00af%2F2026-04-27%20225153.png?generation=1777315934812421&amp;alt=media\" alt=\"\"></p>\n<p>Switching from classification to embedding models for western blots improved the score by +0.017: from 0.353 to <strong>0.370</strong>. Further gains came from improving the western blot overlap embedder by incorporating augmentations based on bounding boxes from the lane detector described below, adding approximately +0.010. The microscopy embedder was more challenging to train due to higher input resolution and the larger dataset size, so I used a cloud machine (A100 or RTX 6000) instead of my local RTX 4090 setup. This provided an additional boost toward the end of the competition, improving the score by +0.015 (from 0.431 to 0.446).</p>\n<hr>\n<h1>The Key Breakthrough: Matching Individual Western Blot Lanes</h1>\n<p>By mid-December, I decided to commit to a time-consuming idea that I believed could reveal duplication patterns missed by panel-level matching: while western blot images are generally unique, each individual lane within a blot should be unique as well. Without clear evidence that this would work, I spent about a week manually annotating bounding boxes for lane detection. In total, I labeled 3,732 images to train a YOLO-based detector for dark regions in western blot images. I then applied this model to the full dataset of ~6M blots, producing approximately 27M bounding boxes:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fcdcfd828a7dad58c9fad610d652f6076%2F2026-04-27%20232637.png?generation=1777318021984840&amp;alt=media\" alt=\"\"></p>\n<p>I reused the pipeline from the western blot “Duplicate” case, modifying only the input resolution and the strength of augmentations. The first checkpoint improved the leaderboard score from 0.375 to 0.397 without any threshold tuning, and further adjustment of the similarity threshold increased it to 0.401 — and that is how I ended up spending New Year’s Eve.</p>\n<p>After cleaning the dataset and tuning the training hyperparameters, I reached a score of <strong>0.416</strong>. At this point, the model started detecting cases like these:</p>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F9bf008b292e573f701ea75201b213a88%2F2026-04-27%20234225.png?generation=1777318975369083&amp;alt=media\" alt=\"\"></p>\n<p>Finally, applying a simple heuristic provided another boost to <strong>0.431</strong>:</p>\n<blockquote>\n  <p>Count the number of lanes in each panel involved in matches.\n  If more than 50% of lanes in a panel are matched, use the entire panel bounding box in the mask instead of individual segment bounding boxes.</p>\n</blockquote>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F881b367125632bfa5a78e326060e6651%2F2026-04-27%20234754.png?generation=1777319300391967&amp;alt=media\" alt=\"\"></p>\n<p>This lane-level component was the main driver of the <strong>jump from 0.375 to 0.431</strong>. The improvement from 0.431 to 0.458 was primarily driven by a stronger microscopy embedding model. Additional gains came from applying TTA, light ensembling, and tuning the inference thresholds for similarity and inliers.</p>\n<hr>\n<h1>Matching</h1>\n<p>Candidates from the embedding stage were passed to a matching module, which filtered out non-relevant microscopy pairs using inlier count and inlier score thresholds. For western blots, all candidates were retained for mask generation, even when no keypoints were matched.</p>\n<p>For microscopy images, SIFT performed surprisingly well, and despite multiple attempts, I was unable to replace it with more modern alternatives. However, SIFT struggled on western blots, often failing to extract reliable keypoints. To address this, I trained a neural matching pipeline using <a href=\"https://github.com/cvg/glue-factory\" target=\"_blank\">Glue Factory</a> (ALIKED-n16 + LightGlue). Compared with SIFT-based matching for western blots, this significantly accelerated inference (from ~2.5 hours to ~20 minutes), while providing only marginal improvement in leaderboard score (+0.005 or less).</p>\n<p><strong>Final setup</strong>:</p>\n<ul>\n<li>Microscopy: LightGlue with SIFT features and RANSAC-based geometric verification</li>\n<li>Western blots: LightGlue with ALIKED features and MAGSAC-based geometric verification</li>\n</ul>\n<p><img src=\"https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb93678952e53b2ad7e71305b00f4984a%2F2026-04-28%20144014.png?generation=1777372849011274&amp;alt=media\" alt=\"\"></p>\n<hr>\n<h1>What Mattered Most</h1>\n<p>The most important factors were:</p>\n<ul>\n<li>Building a clean validation set instead of relying directly on noisy public data.</li>\n<li>Training separate models for western blots and microscopy/macroscopy images, with domain-specific augmentations.</li>\n<li>Moving from classification to embedding-based retrieval.</li>\n<li>Matching individual western blot lanes rather than only full panels.</li>\n<li>Using domain-specific augmentations that reflected real failure cases.</li>\n</ul>\n<hr>\n<h1>Final Thoughts</h1>\n<p>I would like to thank the organizers for providing a real-world test dataset and for hosting the competition on Kaggle, which made it easy to iterate and compare approaches with others.</p>\n<p>This competition reshaped my perspective on scientific image integrity. Before the competition, I assumed that duplication or manipulation in biomedical research was relatively rare. However, as I explored the literature and worked with the data, it became clear that such cases are more prevalent than I initially expected, and that many subtle cases may remain undiscovered.</p>\n<p>One of the clearest indicators of this complexity was the performance gap: improving from the organizers' solution (~0.35 according to the recent post) to the top private leaderboard score (0.55) reflects a substantial gain in detection capability. My evaluation of existing open-source tools (e.g. BusterNet) also suggested that many academic methods do not transfer well to this setting.</p>\n<p>At the same time, I am cautious about how such models should be used in practice. During this work, I encountered many cases that appeared \"<em>more similar than would be expected by chance</em>\", but I would not draw conclusions about the underlying reasons without careful expert analysis. Tools like this should be treated as decision-support systems rather than definitive evidence.</p>\n<p>Finally, I believe that making strong solutions more accessible can have meaningful practical impact. Providing tools that editors and researchers can use in real editorial workflows may help bridge the gap between research and practice.</p>",
      "rawMarkdown": "# Brief Description of the Solution\n\nThe final solution followed the pipeline below:\n\n1. Detect relevant image regions: **Blot** (western blot) and **Microscopy** (microscopy/macroscopy) using YOLO-based panel detectors.\n2. Retrieve suspicious candidate pairs using embedding models trained separately for each data type.\n3. Localize duplicated or overlapping regions using keypoint matching and geometric verification.\n4. If no matches were found, fall back to the best available public Kaggle segmentation kernel, constrained by Blot/Microscopy masks. I initially used @ravaghi’s version and later replaced it with @pankajiitr’s code without modifications. This consistently provided a ~0.005–0.01 improvement, so I included it in both final submissions.\n5. If no evidence was detected, classify the sample as **authentic**.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fba1a1b08fe068b52f212b728270fe808%2F2026-04-29%20183252.png?generation=1777473283918756&alt=media)\n-----\n\n# Code\n\nI packaged the solution as a Python library and published the model weights through a GitHub Release.\n\nCheck out the [vlad3996/forgeryscope repository](https://github.com/vlad3996/forgeryscope) for installation instructions, examples, and pretrained weights.\n\nA single-image inference example is available in [examples/quick_start.ipynb](https://github.com/vlad3996/forgeryscope/blob/main/examples/quick_start.ipynb), and a simplified Kaggle submission pipeline is available [here](https://www.kaggle.com/code/orshanec/1st-place-solution).\n\nThe package includes two YOLO-based detection models, four embedding models, and two keypoint extraction and matching pipelines. The core pipeline logic remains unchanged, with simplifications applied mainly to reduce complexity and improve usability.\n\nCompared to my [best competition solution](https://www.kaggle.com/code/orshanec/1st-place-solution-0-550-private-0-450-public), this simplified release:\n\n* excludes the public segmentation kernel\n* uses a single microscopy embedding model instead of an ensemble of three\n* relies on the best-performing single panel detector rather than an ensemble of two YOLO detectors\n\n-----\n\n# Data\n\nI started by reviewing related publications to better understand the domain and build intuition about the data. This analysis suggested that roughly 50% of reported cases involve western blots, while microscopy and macroscopy images make up a substantial portion of the remainder. For simplicity, I merged microscopy and macroscopy into a single class. Based on this, I focused on two categories (western blots and microscopy/macroscopy images) and deprioritized the rest, as they were less likely to be prominent in the test set. I also experimented with a simple detector for identical [XRD patterns](https://pubpeer.com/publications/739636EFE147869FB290A67B01E5C0), but it had no measurable impact on the leaderboard, so I abandoned this direction. Similarly, I chose not to invest time in inpainting detection, as it appears relatively rare in real publications and I expected an existing public kernel to handle such cases with some degree of success if needed; the same reasoning applied to microscopy images with duplicated cells.\n\nIn parallel, I set up a pipeline to download biomedical papers from [PubMed Central](https://pmc.ncbi.nlm.nih.gov/). By mid-December, I had collected ~2 million .tar.gz archives (~14 TB) with licenses such as CC0 and CC BY. I extracted source images from the PDFs, applied YOLO-based panel detection, and obtained around 6.6M western blot images and 16.3M microscopy/macroscopy images. I then progressively built a deduplicated dataset, aiming to keep only one image per visual identity. In practice, exact duplicates are relatively uncommon in biomedical figures; however, I still identified hundreds of thousands of near-duplicate or even identical samples. Some of these cases have also been reported on [PubPeer](https://pubpeer.com/), although many likely remained unnoticed. This dataset later proved useful for training embedding models.\n\nIn addition to the large training dataset, I created a reliable validation set. It was initially based on BioFors, but I found it insufficient for proper evaluation: many negative pairs were actually positive pairs, and some positive pairs were mislabeled. To address this, I reannotated all positive pairs and as many negative pairs as possible. This became one of the most important parts of the pipeline: without a clean validation set, it was nearly impossible to make reliable progress.\n\n-----\n\n# Baseline\n\nAt the beginning of the competition, I established the initial pipeline structure, which remained largely unchanged throughout. It consisted of the organizers’ YOLO-based panel extractor, followed by candidate filtering using SigLIP2 for microscopy and a binary “similar / non-similar” classifier for western blots. The selected candidates were then passed to a LightGlue-SuperPoint matcher for precise localization (*max_keypoints = 2048, inlier_threshold = 100, match_score_threshold = 0.6*). This setup achieved a score of 0.327. When ensembled with a 0.319 [public kernel](https://www.kaggle.com/code/ravaghi/scientific-image-forgery-detection-dinov2), it reached **0.341**, which was the top score at that stage (with the second-best at 0.324).\n\nLater, I improved the pipeline to 0.342 by replacing SigLIP2 with my binary classifier and retraining YOLO on my own data. Combined with the public kernel, this version reached approximately **0.353**. At that point, the best score was ~0.355 by @melgor, and his presence on the leaderboard motivated me to invest more effort into embedding-based approaches. I also began to observe performance saturation, so I gradually replaced the classification models with embedding models and focused on embeddings for the rest of the competition.\n\nRough progression: \n| Stage | Public LB score |\n|---|---:|\n| Initial pipeline | 0.327 |\n| + public DINOv2 kernel | 0.341 |\n| + SigLIP2 -> custom classifier and retrained YOLO | 0.353 |\n| + western blot embeddings | 0.370 |\n| + SuperPoint -> pretrained SIFT and ALIKED | 0.375 |\n| + lane-level western blot matching | 0.431 |\n| + stronger microscopy embedder / retrained ALIKED / TTA / ensembling / threshold tuning | 0.456–0.458 |\n\n\nNote: values are approximate and based on 250+ submissions; improvements are not strictly incremental.\n\nOne additional note: I experimented with adding a body imaging class, similar to microscopy/macroscopy. On the public leaderboard, this provided a small boost (from 0.456 to 0.458), so I included it in one of the two final submissions. However, on the private leaderboard, it performed worse than the 0.450 submission that did not include body imaging panels:\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb6af6b886ff3a17179ef2e715522f1e1%2F2026-04-28%20230125.png?generation=1777402911753014&alt=media)\n\n-----\n\n# YOLO-based Panel Detector\n\nAs a starting point for the detector, I used the competition organizers’ [detector](https://github.com/phillipecardenuto/upm/tree/main/panel-extractor). However, I quickly encountered several issues, including **missed detections**, **false positives**, and **inconsistent labels**.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F3917a8436b5c13528e6548a412459a9c%2F2026-04-27%20170820.png?generation=1777296062816155&alt=media)\n\nTo address this, I reannotated the dataset to correct inaccurate bounding boxes and labeling errors, and to better align it with the competition requirements (e.g., handling single-panel images). I also sampled images from a large unlabeled biomedical dataset and annotated them. In addition, I analyzed failure cases of the detector and incorporated those examples into the training set.\n\nIn total, this resulted in 11.3k training figures in version 9 of the dataset and 12.3k in version 10. However, leaderboard performance with version 10 was slightly worse, so I stopped further annotation at that point.\n\nThe model was trained using the Ultralytics YOLO library with the following command:\n\n`yolo detect train   model=yolo12x.pt   data=dataset_V9.yaml  epochs=100   imgsz=640   batch=80   translate=0.2  flipud=0.2 mosaic=1 cos_lr=True warmup_epochs=0 optimizer=\"radam\" lr0=0.001`\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F477661ee5ca5c6f2db79fe84d9e1475d%2F2026-04-27%20173634.png?generation=1777297044745513&alt=media)\n\nPanels corresponding to {\"Graphs\", \"Flow Cytometry\", \"Body Imaging\"} were excluded in the best private submission.\n\nTowards the end of the competition, I trained a multiscale variant and used an ensemble of these models for the final submission, which provided a +0.002–0.003 improvement on the leaderboard compared to the single model above:\n\n`yolo detect train   model=yolo12x.pt   data=dataset_V10.yaml  epochs=80   imgsz=640   batch=16   translate=0.2  flipud=0.2 mosaic=1 multi_scale=True`\n\n-----\n# Embeddings\n\nMidway through the competition, I shifted my focus to learning embeddings that maximize separation between similar and dissimilar samples. In this context, I treated two types of relationships separately:\n* **Duplicate** — images that are approximately 80–100% derived from the same source.\n* **Overlap** — images that share roughly 5–100% common origin (with duplicates being a subset of this category).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fd6cd9c1c639121bf64eefd1eb9695445%2F2026-04-27%20215210.png?generation=1777312401814733&alt=media)\n\n\nI used deduplicated datasets for western blot and microscopy/macroscopy images, investing significant effort to ensure high data quality, under the assumption that each image appears only once. I started with [InfoNCE loss](https://arxiv.org/pdf/1807.03748), but then switched to [SupCon](https://arxiv.org/pdf/2004.11362). For each batch, I sampled N images and generated one augmented version per image, resulting in 2N samples. This produced N positive (similar) pairs, while all remaining pairs within the batch were treated as non-similar. I experimented with various alternative metric learning losses, but they consistently underperformed, so I ultimately adopted `SupCon` as the primary training objective, using a temperature between 0.07 and 0.09 and the largest batch size permitted by GPU memory. I used [nextvit_small](https://huggingface.co/timm/nextvit_small.bd_ssld_6m_in1k) as the backbone for each model toward the end of the competition. \n\nFor western blot “Duplicate” cases, I applied augmentations inspired by real-world variations. I used the [albumentations](https://github.com/albumentations-team/albumentations) library for the following transformations:\n\n* geometric transformations: shifts, scaling, small rotations, perspective transforms, and grid distortions\n* flips and rotations: horizontal/vertical flips and 180° rotation\n* color transformations: inversion, CLAHE, and grayscale conversion\n* image quality degradation: blur and compression\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F4e975dfb57582f524f7943e6c3ccda27%2F2026-04-27%20222419.png?generation=1777314283147218&alt=media)\n\nFor the western blot “Overlap” case, I initially relied on simple heuristics, but later replaced them with bounding boxes from a YOLO-based lane detector. The goal was to identify two views of an image that share at least one common black “lane.” This could correspond either to a simple overlap or to a “zoom” scenario, where one image is nested within another. The cropping process was then restricted to the green-highlighted regions shown below, corresponding to the gaps between black lanes in the western blot image:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fefe2cfa0c7604b4cf3e14fee65ce2a07%2F2026-04-27%20220956.png?generation=1777313425171714&alt=media)\n\nFor microscopy/macroscopy images, I designed custom augmentations to simulate common artifacts observed in real data. These included adding “noisy” elements such as letters, bounding boxes, zoom callouts, arrows, and lines, along with CoarseDropout (inspired by real inpainting and cell copy-paste examples that my model struggled to handle correctly). Since not all images were suitable for generating “overlap” or “zoom” crops, I clustered the dataset and manually labeled the clusters into two categories: suitable for cropping and not suitable. \n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F40b20a9c68f077fffaf41823e94f00af%2F2026-04-27%20225153.png?generation=1777315934812421&alt=media)\n\n\nSwitching from classification to embedding models for western blots improved the score by +0.017: from 0.353 to **0.370**. Further gains came from improving the western blot overlap embedder by incorporating augmentations based on bounding boxes from the lane detector described below, adding approximately +0.010. The microscopy embedder was more challenging to train due to higher input resolution and the larger dataset size, so I used a cloud machine (A100 or RTX 6000) instead of my local RTX 4090 setup. This provided an additional boost toward the end of the competition, improving the score by +0.015 (from 0.431 to 0.446).\n\n-----\n# The Key Breakthrough: Matching Individual Western Blot Lanes\n\nBy mid-December, I decided to commit to a time-consuming idea that I believed could reveal duplication patterns missed by panel-level matching: while western blot images are generally unique, each individual lane within a blot should be unique as well. Without clear evidence that this would work, I spent about a week manually annotating bounding boxes for lane detection. In total, I labeled 3,732 images to train a YOLO-based detector for dark regions in western blot images. I then applied this model to the full dataset of ~6M blots, producing approximately 27M bounding boxes:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fcdcfd828a7dad58c9fad610d652f6076%2F2026-04-27%20232637.png?generation=1777318021984840&alt=media)\n\nI reused the pipeline from the western blot “Duplicate” case, modifying only the input resolution and the strength of augmentations. The first checkpoint improved the leaderboard score from 0.375 to 0.397 without any threshold tuning, and further adjustment of the similarity threshold increased it to 0.401 — and that is how I ended up spending New Year’s Eve.\n\nAfter cleaning the dataset and tuning the training hyperparameters, I reached a score of **0.416**. At this point, the model started detecting cases like these:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F9bf008b292e573f701ea75201b213a88%2F2026-04-27%20234225.png?generation=1777318975369083&alt=media)\n\nFinally, applying a simple heuristic provided another boost to **0.431**:\n\n> Count the number of lanes in each panel involved in matches.\n> If more than 50% of lanes in a panel are matched, use the entire panel bounding box in the mask instead of individual segment bounding boxes.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F881b367125632bfa5a78e326060e6651%2F2026-04-27%20234754.png?generation=1777319300391967&alt=media)\n\nThis lane-level component was the main driver of the **jump from 0.375 to 0.431**. The improvement from 0.431 to 0.458 was primarily driven by a stronger microscopy embedding model. Additional gains came from applying TTA, light ensembling, and tuning the inference thresholds for similarity and inliers.\n\n-----\n# Matching\n\nCandidates from the embedding stage were passed to a matching module, which filtered out non-relevant microscopy pairs using inlier count and inlier score thresholds. For western blots, all candidates were retained for mask generation, even when no keypoints were matched.\n\nFor microscopy images, SIFT performed surprisingly well, and despite multiple attempts, I was unable to replace it with more modern alternatives. However, SIFT struggled on western blots, often failing to extract reliable keypoints. To address this, I trained a neural matching pipeline using [Glue Factory](https://github.com/cvg/glue-factory) (ALIKED-n16 + LightGlue). Compared with SIFT-based matching for western blots, this significantly accelerated inference (from ~2.5 hours to ~20 minutes), while providing only marginal improvement in leaderboard score (+0.005 or less).\n\n**Final setup**:\n\n* Microscopy: LightGlue with SIFT features and RANSAC-based geometric verification\n* Western blots: LightGlue with ALIKED features and MAGSAC-based geometric verification\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb93678952e53b2ad7e71305b00f4984a%2F2026-04-28%20144014.png?generation=1777372849011274&alt=media)\n\n-----\n# What Mattered Most\n\nThe most important factors were:\n\n* Building a clean validation set instead of relying directly on noisy public data.\n* Training separate models for western blots and microscopy/macroscopy images, with domain-specific augmentations.\n* Moving from classification to embedding-based retrieval.\n* Matching individual western blot lanes rather than only full panels.\n* Using domain-specific augmentations that reflected real failure cases.\n\n-----\n\n# Final Thoughts\n\nI would like to thank the organizers for providing a real-world test dataset and for hosting the competition on Kaggle, which made it easy to iterate and compare approaches with others.\n\nThis competition reshaped my perspective on scientific image integrity. Before the competition, I assumed that duplication or manipulation in biomedical research was relatively rare. However, as I explored the literature and worked with the data, it became clear that such cases are more prevalent than I initially expected, and that many subtle cases may remain undiscovered.\n\nOne of the clearest indicators of this complexity was the performance gap: improving from the organizers' solution (~0.35 according to the recent post) to the top private leaderboard score (0.55) reflects a substantial gain in detection capability. My evaluation of existing open-source tools (e.g. BusterNet) also suggested that many academic methods do not transfer well to this setting.\n\nAt the same time, I am cautious about how such models should be used in practice. During this work, I encountered many cases that appeared \"*more similar than would be expected by chance*\", but I would not draw conclusions about the underlying reasons without careful expert analysis. Tools like this should be treated as decision-support systems rather than definitive evidence.\n\nFinally, I believe that making strong solutions more accessible can have meaningful practical impact. Providing tools that editors and researchers can use in real editorial workflows may help bridge the gap between research and practice.",
      "votes": null
    },
    {
      "id": "3450778",
      "postDate": "04/30/2026 12:04:35",
      "content": "<p>Incredible writeup. You put in a ton of effort. I did not know about Glue Factory. For embeddings, I tried building a Siamese NN classifier but ultimately did not work for me. Well done👑</p>",
      "rawMarkdown": "Incredible writeup. You put in a ton of effort. I did not know about Glue Factory. For embeddings, I tried building a Siamese NN classifier but ultimately did not work for me. Well done👑",
      "votes": null
    },
    {
      "id": "3453382",
      "postDate": "05/05/2026 12:41:19",
      "content": "<p>Thank you Uladzislau, this is a great writeup! It was a pleasure to compete with you for the last few months.</p>",
      "rawMarkdown": "Thank you Uladzislau, this is a great writeup! It was a pleasure to compete with you for the last few months.",
      "votes": null
    },
    {
      "id": "3454613",
      "postDate": "05/07/2026 14:56:41",
      "content": "<p>That's an amazing solution! Congrats on the solo win and thanks for the detailed writeup! 🔥</p>\n<p>Did you try submitting only with matches from the embedding models, i.e., without using LightGlue+SIFT later on the candidates? AFAIU, this would not allow for overlap forgeries detections since the embeddings would capture the entire panel but what about panel/blot-level duplication? Would embedding models be enough in this case?</p>",
      "rawMarkdown": "That's an amazing solution! Congrats on the solo win and thanks for the detailed writeup! 🔥\n\nDid you try submitting only with matches from the embedding models, i.e., without using LightGlue+SIFT later on the candidates? AFAIU, this would not allow for overlap forgeries detections since the embeddings would capture the entire panel but what about panel/blot-level duplication? Would embedding models be enough in this case?",
      "votes": null
    },
    {
      "id": "3455863",
      "postDate": "05/10/2026 13:56:25",
      "content": "<p>Added a new transform to AlbumentationsX, based on the solution: <a href=\"https://explore.albumentations.ai/transform/AnnotationArtifacts\" target=\"_blank\">https://explore.albumentations.ai/transform/AnnotationArtifacts</a></p>",
      "rawMarkdown": "Added a new transform to AlbumentationsX, based on the solution: https://explore.albumentations.ai/transform/AnnotationArtifacts",
      "votes": null
    },
    {
      "id": "3509698",
      "postDate": "08/06/2026 21:35:50",
      "content": "<p>You are so hardworking, you basically made this entire solution solo from scratch, hats off to you dude 🫡 very impressive work!</p>",
      "rawMarkdown": "You are so hardworking, you basically made this entire solution solo from scratch, hats off to you dude 🫡 very impressive work!",
      "votes": null
    }
  ],
  "comments": [
    {
      "id": 3450778,
      "author_name": "returnofsputnik",
      "author_url": "",
      "post_date": "04/30/2026 12:04:35",
      "content": "<p>Incredible writeup. You put in a ton of effort. I did not know about Glue Factory. For embeddings, I tried building a Siamese NN classifier but ultimately did not work for me. Well done👑</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3453382,
      "author_name": "mohamedidrissighalmi",
      "author_url": "",
      "post_date": "05/05/2026 12:41:19",
      "content": "<p>Thank you Uladzislau, this is a great writeup! It was a pleasure to compete with you for the last few months.</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3454613,
      "author_name": "arc144",
      "author_url": "",
      "post_date": "05/07/2026 14:56:41",
      "content": "<p>That's an amazing solution! Congrats on the solo win and thanks for the detailed writeup! 🔥</p>\n<p>Did you try submitting only with matches from the embedding models, i.e., without using LightGlue+SIFT later on the candidates? AFAIU, this would not allow for overlap forgeries detections since the embeddings would capture the entire panel but what about panel/blot-level duplication? Would embedding models be enough in this case?</p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3455863,
      "author_name": "iglovikov",
      "author_url": "",
      "post_date": "05/10/2026 13:56:25",
      "content": "<p>Added a new transform to AlbumentationsX, based on the solution: <a href=\"https://explore.albumentations.ai/transform/AnnotationArtifacts\" target=\"_blank\">https://explore.albumentations.ai/transform/AnnotationArtifacts</a></p>",
      "votes": null,
      "replies": []
    },
    {
      "id": 3509698,
      "author_name": "mrearthworm",
      "author_url": "",
      "post_date": "08/06/2026 21:35:50",
      "content": "<p>You are so hardworking, you basically made this entire solution solo from scratch, hats off to you dude 🫡 very impressive work!</p>",
      "votes": null,
      "replies": []
    }
  ],
  "raw_markdown_by_id": {
    "3450647": "# Brief Description of the Solution\n\nThe final solution followed the pipeline below:\n\n1. Detect relevant image regions: **Blot** (western blot) and **Microscopy** (microscopy/macroscopy) using YOLO-based panel detectors.\n2. Retrieve suspicious candidate pairs using embedding models trained separately for each data type.\n3. Localize duplicated or overlapping regions using keypoint matching and geometric verification.\n4. If no matches were found, fall back to the best available public Kaggle segmentation kernel, constrained by Blot/Microscopy masks. I initially used @ravaghi’s version and later replaced it with @pankajiitr’s code without modifications. This consistently provided a ~0.005–0.01 improvement, so I included it in both final submissions.\n5. If no evidence was detected, classify the sample as **authentic**.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fba1a1b08fe068b52f212b728270fe808%2F2026-04-29%20183252.png?generation=1777473283918756&alt=media)\n-----\n\n# Code\n\nI packaged the solution as a Python library and published the model weights through a GitHub Release.\n\nCheck out the [vlad3996/forgeryscope repository](https://github.com/vlad3996/forgeryscope) for installation instructions, examples, and pretrained weights.\n\nA single-image inference example is available in [examples/quick_start.ipynb](https://github.com/vlad3996/forgeryscope/blob/main/examples/quick_start.ipynb), and a simplified Kaggle submission pipeline is available [here](https://www.kaggle.com/code/orshanec/1st-place-solution).\n\nThe package includes two YOLO-based detection models, four embedding models, and two keypoint extraction and matching pipelines. The core pipeline logic remains unchanged, with simplifications applied mainly to reduce complexity and improve usability.\n\nCompared to my [best competition solution](https://www.kaggle.com/code/orshanec/1st-place-solution-0-550-private-0-450-public), this simplified release:\n\n* excludes the public segmentation kernel\n* uses a single microscopy embedding model instead of an ensemble of three\n* relies on the best-performing single panel detector rather than an ensemble of two YOLO detectors\n\n-----\n\n# Data\n\nI started by reviewing related publications to better understand the domain and build intuition about the data. This analysis suggested that roughly 50% of reported cases involve western blots, while microscopy and macroscopy images make up a substantial portion of the remainder. For simplicity, I merged microscopy and macroscopy into a single class. Based on this, I focused on two categories (western blots and microscopy/macroscopy images) and deprioritized the rest, as they were less likely to be prominent in the test set. I also experimented with a simple detector for identical [XRD patterns](https://pubpeer.com/publications/739636EFE147869FB290A67B01E5C0), but it had no measurable impact on the leaderboard, so I abandoned this direction. Similarly, I chose not to invest time in inpainting detection, as it appears relatively rare in real publications and I expected an existing public kernel to handle such cases with some degree of success if needed; the same reasoning applied to microscopy images with duplicated cells.\n\nIn parallel, I set up a pipeline to download biomedical papers from [PubMed Central](https://pmc.ncbi.nlm.nih.gov/). By mid-December, I had collected ~2 million .tar.gz archives (~14 TB) with licenses such as CC0 and CC BY. I extracted source images from the PDFs, applied YOLO-based panel detection, and obtained around 6.6M western blot images and 16.3M microscopy/macroscopy images. I then progressively built a deduplicated dataset, aiming to keep only one image per visual identity. In practice, exact duplicates are relatively uncommon in biomedical figures; however, I still identified hundreds of thousands of near-duplicate or even identical samples. Some of these cases have also been reported on [PubPeer](https://pubpeer.com/), although many likely remained unnoticed. This dataset later proved useful for training embedding models.\n\nIn addition to the large training dataset, I created a reliable validation set. It was initially based on BioFors, but I found it insufficient for proper evaluation: many negative pairs were actually positive pairs, and some positive pairs were mislabeled. To address this, I reannotated all positive pairs and as many negative pairs as possible. This became one of the most important parts of the pipeline: without a clean validation set, it was nearly impossible to make reliable progress.\n\n-----\n\n# Baseline\n\nAt the beginning of the competition, I established the initial pipeline structure, which remained largely unchanged throughout. It consisted of the organizers’ YOLO-based panel extractor, followed by candidate filtering using SigLIP2 for microscopy and a binary “similar / non-similar” classifier for western blots. The selected candidates were then passed to a LightGlue-SuperPoint matcher for precise localization (*max_keypoints = 2048, inlier_threshold = 100, match_score_threshold = 0.6*). This setup achieved a score of 0.327. When ensembled with a 0.319 [public kernel](https://www.kaggle.com/code/ravaghi/scientific-image-forgery-detection-dinov2), it reached **0.341**, which was the top score at that stage (with the second-best at 0.324).\n\nLater, I improved the pipeline to 0.342 by replacing SigLIP2 with my binary classifier and retraining YOLO on my own data. Combined with the public kernel, this version reached approximately **0.353**. At that point, the best score was ~0.355 by @melgor, and his presence on the leaderboard motivated me to invest more effort into embedding-based approaches. I also began to observe performance saturation, so I gradually replaced the classification models with embedding models and focused on embeddings for the rest of the competition.\n\nRough progression: \n| Stage | Public LB score |\n|---|---:|\n| Initial pipeline | 0.327 |\n| + public DINOv2 kernel | 0.341 |\n| + SigLIP2 -> custom classifier and retrained YOLO | 0.353 |\n| + western blot embeddings | 0.370 |\n| + SuperPoint -> pretrained SIFT and ALIKED | 0.375 |\n| + lane-level western blot matching | 0.431 |\n| + stronger microscopy embedder / retrained ALIKED / TTA / ensembling / threshold tuning | 0.456–0.458 |\n\n\nNote: values are approximate and based on 250+ submissions; improvements are not strictly incremental.\n\nOne additional note: I experimented with adding a body imaging class, similar to microscopy/macroscopy. On the public leaderboard, this provided a small boost (from 0.456 to 0.458), so I included it in one of the two final submissions. However, on the private leaderboard, it performed worse than the 0.450 submission that did not include body imaging panels:\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb6af6b886ff3a17179ef2e715522f1e1%2F2026-04-28%20230125.png?generation=1777402911753014&alt=media)\n\n-----\n\n# YOLO-based Panel Detector\n\nAs a starting point for the detector, I used the competition organizers’ [detector](https://github.com/phillipecardenuto/upm/tree/main/panel-extractor). However, I quickly encountered several issues, including **missed detections**, **false positives**, and **inconsistent labels**.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F3917a8436b5c13528e6548a412459a9c%2F2026-04-27%20170820.png?generation=1777296062816155&alt=media)\n\nTo address this, I reannotated the dataset to correct inaccurate bounding boxes and labeling errors, and to better align it with the competition requirements (e.g., handling single-panel images). I also sampled images from a large unlabeled biomedical dataset and annotated them. In addition, I analyzed failure cases of the detector and incorporated those examples into the training set.\n\nIn total, this resulted in 11.3k training figures in version 9 of the dataset and 12.3k in version 10. However, leaderboard performance with version 10 was slightly worse, so I stopped further annotation at that point.\n\nThe model was trained using the Ultralytics YOLO library with the following command:\n\n`yolo detect train   model=yolo12x.pt   data=dataset_V9.yaml  epochs=100   imgsz=640   batch=80   translate=0.2  flipud=0.2 mosaic=1 cos_lr=True warmup_epochs=0 optimizer=\"radam\" lr0=0.001`\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F477661ee5ca5c6f2db79fe84d9e1475d%2F2026-04-27%20173634.png?generation=1777297044745513&alt=media)\n\nPanels corresponding to {\"Graphs\", \"Flow Cytometry\", \"Body Imaging\"} were excluded in the best private submission.\n\nTowards the end of the competition, I trained a multiscale variant and used an ensemble of these models for the final submission, which provided a +0.002–0.003 improvement on the leaderboard compared to the single model above:\n\n`yolo detect train   model=yolo12x.pt   data=dataset_V10.yaml  epochs=80   imgsz=640   batch=16   translate=0.2  flipud=0.2 mosaic=1 multi_scale=True`\n\n-----\n# Embeddings\n\nMidway through the competition, I shifted my focus to learning embeddings that maximize separation between similar and dissimilar samples. In this context, I treated two types of relationships separately:\n* **Duplicate** — images that are approximately 80–100% derived from the same source.\n* **Overlap** — images that share roughly 5–100% common origin (with duplicates being a subset of this category).\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fd6cd9c1c639121bf64eefd1eb9695445%2F2026-04-27%20215210.png?generation=1777312401814733&alt=media)\n\n\nI used deduplicated datasets for western blot and microscopy/macroscopy images, investing significant effort to ensure high data quality, under the assumption that each image appears only once. I started with [InfoNCE loss](https://arxiv.org/pdf/1807.03748), but then switched to [SupCon](https://arxiv.org/pdf/2004.11362). For each batch, I sampled N images and generated one augmented version per image, resulting in 2N samples. This produced N positive (similar) pairs, while all remaining pairs within the batch were treated as non-similar. I experimented with various alternative metric learning losses, but they consistently underperformed, so I ultimately adopted `SupCon` as the primary training objective, using a temperature between 0.07 and 0.09 and the largest batch size permitted by GPU memory. I used [nextvit_small](https://huggingface.co/timm/nextvit_small.bd_ssld_6m_in1k) as the backbone for each model toward the end of the competition. \n\nFor western blot “Duplicate” cases, I applied augmentations inspired by real-world variations. I used the [albumentations](https://github.com/albumentations-team/albumentations) library for the following transformations:\n\n* geometric transformations: shifts, scaling, small rotations, perspective transforms, and grid distortions\n* flips and rotations: horizontal/vertical flips and 180° rotation\n* color transformations: inversion, CLAHE, and grayscale conversion\n* image quality degradation: blur and compression\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F4e975dfb57582f524f7943e6c3ccda27%2F2026-04-27%20222419.png?generation=1777314283147218&alt=media)\n\nFor the western blot “Overlap” case, I initially relied on simple heuristics, but later replaced them with bounding boxes from a YOLO-based lane detector. The goal was to identify two views of an image that share at least one common black “lane.” This could correspond either to a simple overlap or to a “zoom” scenario, where one image is nested within another. The cropping process was then restricted to the green-highlighted regions shown below, corresponding to the gaps between black lanes in the western blot image:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fefe2cfa0c7604b4cf3e14fee65ce2a07%2F2026-04-27%20220956.png?generation=1777313425171714&alt=media)\n\nFor microscopy/macroscopy images, I designed custom augmentations to simulate common artifacts observed in real data. These included adding “noisy” elements such as letters, bounding boxes, zoom callouts, arrows, and lines, along with CoarseDropout (inspired by real inpainting and cell copy-paste examples that my model struggled to handle correctly). Since not all images were suitable for generating “overlap” or “zoom” crops, I clustered the dataset and manually labeled the clusters into two categories: suitable for cropping and not suitable. \n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F40b20a9c68f077fffaf41823e94f00af%2F2026-04-27%20225153.png?generation=1777315934812421&alt=media)\n\n\nSwitching from classification to embedding models for western blots improved the score by +0.017: from 0.353 to **0.370**. Further gains came from improving the western blot overlap embedder by incorporating augmentations based on bounding boxes from the lane detector described below, adding approximately +0.010. The microscopy embedder was more challenging to train due to higher input resolution and the larger dataset size, so I used a cloud machine (A100 or RTX 6000) instead of my local RTX 4090 setup. This provided an additional boost toward the end of the competition, improving the score by +0.015 (from 0.431 to 0.446).\n\n-----\n# The Key Breakthrough: Matching Individual Western Blot Lanes\n\nBy mid-December, I decided to commit to a time-consuming idea that I believed could reveal duplication patterns missed by panel-level matching: while western blot images are generally unique, each individual lane within a blot should be unique as well. Without clear evidence that this would work, I spent about a week manually annotating bounding boxes for lane detection. In total, I labeled 3,732 images to train a YOLO-based detector for dark regions in western blot images. I then applied this model to the full dataset of ~6M blots, producing approximately 27M bounding boxes:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fcdcfd828a7dad58c9fad610d652f6076%2F2026-04-27%20232637.png?generation=1777318021984840&alt=media)\n\nI reused the pipeline from the western blot “Duplicate” case, modifying only the input resolution and the strength of augmentations. The first checkpoint improved the leaderboard score from 0.375 to 0.397 without any threshold tuning, and further adjustment of the similarity threshold increased it to 0.401 — and that is how I ended up spending New Year’s Eve.\n\nAfter cleaning the dataset and tuning the training hyperparameters, I reached a score of **0.416**. At this point, the model started detecting cases like these:\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F9bf008b292e573f701ea75201b213a88%2F2026-04-27%20234225.png?generation=1777318975369083&alt=media)\n\nFinally, applying a simple heuristic provided another boost to **0.431**:\n\n> Count the number of lanes in each panel involved in matches.\n> If more than 50% of lanes in a panel are matched, use the entire panel bounding box in the mask instead of individual segment bounding boxes.\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2F881b367125632bfa5a78e326060e6651%2F2026-04-27%20234754.png?generation=1777319300391967&alt=media)\n\nThis lane-level component was the main driver of the **jump from 0.375 to 0.431**. The improvement from 0.431 to 0.458 was primarily driven by a stronger microscopy embedding model. Additional gains came from applying TTA, light ensembling, and tuning the inference thresholds for similarity and inliers.\n\n-----\n# Matching\n\nCandidates from the embedding stage were passed to a matching module, which filtered out non-relevant microscopy pairs using inlier count and inlier score thresholds. For western blots, all candidates were retained for mask generation, even when no keypoints were matched.\n\nFor microscopy images, SIFT performed surprisingly well, and despite multiple attempts, I was unable to replace it with more modern alternatives. However, SIFT struggled on western blots, often failing to extract reliable keypoints. To address this, I trained a neural matching pipeline using [Glue Factory](https://github.com/cvg/glue-factory) (ALIKED-n16 + LightGlue). Compared with SIFT-based matching for western blots, this significantly accelerated inference (from ~2.5 hours to ~20 minutes), while providing only marginal improvement in leaderboard score (+0.005 or less).\n\n**Final setup**:\n\n* Microscopy: LightGlue with SIFT features and RANSAC-based geometric verification\n* Western blots: LightGlue with ALIKED features and MAGSAC-based geometric verification\n\n![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F29607288%2Fb93678952e53b2ad7e71305b00f4984a%2F2026-04-28%20144014.png?generation=1777372849011274&alt=media)\n\n-----\n# What Mattered Most\n\nThe most important factors were:\n\n* Building a clean validation set instead of relying directly on noisy public data.\n* Training separate models for western blots and microscopy/macroscopy images, with domain-specific augmentations.\n* Moving from classification to embedding-based retrieval.\n* Matching individual western blot lanes rather than only full panels.\n* Using domain-specific augmentations that reflected real failure cases.\n\n-----\n\n# Final Thoughts\n\nI would like to thank the organizers for providing a real-world test dataset and for hosting the competition on Kaggle, which made it easy to iterate and compare approaches with others.\n\nThis competition reshaped my perspective on scientific image integrity. Before the competition, I assumed that duplication or manipulation in biomedical research was relatively rare. However, as I explored the literature and worked with the data, it became clear that such cases are more prevalent than I initially expected, and that many subtle cases may remain undiscovered.\n\nOne of the clearest indicators of this complexity was the performance gap: improving from the organizers' solution (~0.35 according to the recent post) to the top private leaderboard score (0.55) reflects a substantial gain in detection capability. My evaluation of existing open-source tools (e.g. BusterNet) also suggested that many academic methods do not transfer well to this setting.\n\nAt the same time, I am cautious about how such models should be used in practice. During this work, I encountered many cases that appeared \"*more similar than would be expected by chance*\", but I would not draw conclusions about the underlying reasons without careful expert analysis. Tools like this should be treated as decision-support systems rather than definitive evidence.\n\nFinally, I believe that making strong solutions more accessible can have meaningful practical impact. Providing tools that editors and researchers can use in real editorial workflows may help bridge the gap between research and practice.",
    "3450778": "Incredible writeup. You put in a ton of effort. I did not know about Glue Factory. For embeddings, I tried building a Siamese NN classifier but ultimately did not work for me. Well done👑",
    "3453382": "Thank you Uladzislau, this is a great writeup! It was a pleasure to compete with you for the last few months.",
    "3454613": "That's an amazing solution! Congrats on the solo win and thanks for the detailed writeup! 🔥\n\nDid you try submitting only with matches from the embedding models, i.e., without using LightGlue+SIFT later on the candidates? AFAIU, this would not allow for overlap forgeries detections since the embeddings would capture the entire panel but what about panel/blot-level duplication? Would embedding models be enough in this case?",
    "3455863": "Added a new transform to AlbumentationsX, based on the solution: https://explore.albumentations.ai/transform/AnnotationArtifacts",
    "3509698": "You are so hardworking, you basically made this entire solution solo from scratch, hats off to you dude 🫡 very impressive work!"
  },
  "source": "meta"
}