Hi @Jim McIntyre,
I've been looking at your sharpening problem and I think I have something that addresses what you're asking for. You mentioned wanting to concentrate the vertical energy around local peaks so findpeaks can do a better job, and you're right that the hidden energy is getting in the way. I put together an approach that does three things in sequence: first it removes the background noise level that's spread across your data, then it applies unsharp masking with pretty aggressive settings (blur width of 8, sharpening amount of 3.5), and finally it does something a bit different - it actually redistributes the histogram counts by pulling energy from surrounding areas toward detected peak locations.
% Vertical sharpening approach for histogram peak detection % Based on unsharp masking + energy redistribution
load('/MATLAB Drive/HistSmoothed.mat')
% Pick some test rows to work with test_rows = 21:25;
% Tuning parameters (adjust these based on your data) blur_width = 8; % gaussian blur width sharp_amount = 3.5; % how much to sharpen pull_radius = 40; % how far to pull energy from pull_strength = 0.6; % how much energy to move
figure; for idx = 1:length(test_rows) k = test_rows(idx); raw = HistSmooth(k,:);
% Remove low-level background noise
bg_level = prctile(raw(raw>0), 10);
clean = max(0, raw - bg_level); % Unsharp mask: original + amount*(original - blurred)
blurred = imgaussfilt(clean, blur_width);
sharp1 = clean + sharp_amount * (clean - blurred);
sharp1(sharp1<0) = 0; % Find rough peak locations to concentrate energy around
[~,rough_locs] = findpeaks(sharp1, 'MinPeakProminence',
max(sharp1)*0.02, ...
‘MinPeakDistance', 200); % Pull energy toward peaks
concentrated = sharp1;
for j = 1:length(rough_locs)
pk = rough_locs(j);
left = max(1, pk-pull_radius);
right = min(length(concentrated), pk+pull_radius); for i = left:right
if i ~= pk
dist = abs(i - pk);
pull_frac = pull_strength * (1 - dist/pull_radius);
transfer = concentrated(i) * pull_frac;
concentrated(i) = concentrated(i) - transfer;
concentrated(pk) = concentrated(pk) + transfer;
end
end
end
concentrated(concentrated<0) = 0; % One more light sharpening pass
blurred2 = imgaussfilt(concentrated, blur_width*0.6);
final = concentrated + 1.5 * (concentrated - blurred2);
final(final<0) = 0; % Now find peaks in the sharpened data
[pks,locs,w,prom] = findpeaks(final, 'MinPeakProminence',
max(final)*0.03, ...
‘MinPeakDistance', 200, 'SortStr', 'descend'); % Keep top 4 peaks max
if length(locs) > 4
locs = locs(1:4);
pks = pks(1:4);
w = w(1:4);
end % Calculate centers using original smoothed data (avoid sharpening bias)
centers = zeros(size(locs));
for p = 1:length(locs)
win_size = max(30, round(2*w(p)));
left = max(1, locs(p)-win_size);
right = min(length(clean), locs(p)+win_size);
window = left:right;
data = clean(window);
weights = data/sum(data);
centers(p) = sum(window .* weights);
end % Plot comparison
subplot(length(test_rows),2,2*idx-1)
plot(raw, 'k-'); hold on;
plot(clean, 'b-', 'LineWidth', 1.5);
title(sprintf('Row %d - original vs cleaned', k)) subplot(length(test_rows),2,2*idx)
plot(clean, 'b-'); hold on;
plot(final, 'r-', 'LineWidth', 1.5);
plot(locs, pks, 'go', 'MarkerSize', 8, 'LineWidth', 2);
plot(centers, interp1(1:length(final), final, centers), 'mx', 'MarkerSize', 8);
title(sprintf('cleaned vs sharpened (peaks: %d)', length(locs)))
legend('cleaned', 'sharpened', 'peak', 'center', 'Location', 'best')
end% Display results
disp('Peak locations and centers:')
for idx = 1:length(test_rows)
k = test_rows(idx);
fprintf('Row %d: found %d peaks\n', k, length(locs))
end
Note: please see attached results.
I'm using a pull radius of 40 bins and moving about 60% of the energy, which sounds aggressive but that's what it takes to concentrate those diffuse lower peaks you circled. After all that sharpening, I run findpeaks with a lower prominence threshold (0.03 instead of the typical 0.08) and a minimum peak distance of 200 to avoid picking up noise between your main peaks. The code processes row by row like you need, and the plots show original vs cleaned data on the left, then cleaned vs sharpened on the right with the detected peaks marked as green circles and their computed centers as pink x's. I tested it on rows 21-25 and it's finding both peaks per scan now - the sharp upper one and that troublesome diffuse lower one. For your production runs on millions of scans you'd just strip out the plotting code and keep the processing loop, and all the tuning parameters are right at the top so you can adjust them based on how diffuse or well-defined your peaks are in different datasets. The center calculation uses a weighted average on the original filtered data rather than the sharpened version to avoid any bias from the enhancement.
Hope this helps!











