Comments (7)
@perrygeo Still interested in this? If so, I've included the basic approach below for reference (not yet cleaned up, just as FYI). What it does is:
- Accept
ref_paths
as a list of paths rather than just a single image. - Add a dimension to the
ref
array for the nth image in the listref_paths
- Load every image from
ref_paths
into the ref array - Once these are all loaded, we then loop through
ref_paths
again and treat every image passed as thesrc
array - The current histogram algorithm handles the binning of the
ref
array without any changes (even with the added dimension) - This creates a new
src
image that uses the histogram binning across all of the images inref_paths
, so they should all share the same histogram.
The open questions for integration are:
- How to expose this functionality in the CLI
- How much refactoring to do so that the existing
hist_match_worker
and the newbatch_hist_match_worker
rely on the same code (which, as you can see below, is duplicated)
def batch_hist_match_worker(ref_paths, match_proportion,
creation_options, bands, color_space, plot,
masked=True,
dst_suffix='_hist_matched'):
"""Matches the histogram of every image in ref_paths
to the average histogram across all of the included images.
Outputs each file with _hist_matched appended to the filename.
Modified from:
https://github.com/mapbox/rio-hist/blob/81e3d5f0f59ba1e4e15f8850592a818501957ed2/rio_hist/match.py
"""
# we need one sample to setup the processing
src_path = ref_paths[0]
with rasterio.open(src_path) as src:
profile = src.profile.copy()
src_arr = src.read(masked=masked)
if src_arr.dtype != np.float32:
raise("Currently, this only supports float32")
if masked:
src_mask, src_fill = calculate_mask(src, src_arr)
src_arr = src_arr.filled()
else:
src_mask, src_fill = None, None
ref_out = np.zeros([len(ref_paths)] + list(src_arr.shape), dtype=src_arr.dtype)
# no bands, just masks
ref_mask_out = np.zeros([len(ref_paths)] + list(src_arr.shape[1:]), dtype=src_arr.dtype)
n_bands = src_arr.shape[0]
for i, ref_path in enumerate(ref_paths):
with rasterio.open(ref_path, 'r') as ref:
ref_arr = ref.read(masked=masked)
if masked:
ref_mask, ref_fill = calculate_mask(ref, ref_arr)
ref_arr = ref_arr.filled()
else:
ref_mask, ref_fill = None, None
ref_out[i, :, :, :] = ref_arr
if ref_mask is None:
ref_mask_out = None
else:
ref_mask_out[i, :, :] = ref_mask
bixs = tuple([int(x) - 1 for x in bands.split(',')])
ref = ref_out
output_paths = []
print("Matching histograms...")
for src_path in tqdm(ref_paths):
with rasterio.open(src_path) as src:
profile = src.profile.copy()
src_arr = src.read(masked=masked)
if masked:
src_mask, src_fill = calculate_mask(src, src_arr)
src_arr = src_arr.filled()
else:
src_mask, src_fill = None, None
src = src_arr
target = src.copy()
for i, b in enumerate(bixs):
logger.debug("Processing band {}".format(b))
src_band = src[b]
ref_band = ref[:, b, :, :]
# Re-apply 2D mask to each band
if src_mask is not None:
logger.debug("apply src_mask to band {}".format(b))
src_band = np.ma.asarray(src_band)
src_band.mask = src_mask
src_band.fill_value = src_fill
if ref_mask_out is not None:
logger.debug("apply ref_mask to band {}".format(b))
ref_band = np.ma.asarray(ref_band)
ref_band.mask = ref_mask_out
ref_band.fill_value = ref_fill
target[b] = histogram_match(src_band, ref_band, match_proportion)
target_rgb = target # cs_backward(target, color_space)
# re-apply src_mask to target_rgb and write ndv
if src_mask is not None:
logger.debug("apply src_mask to target_rgb")
if not np.ma.is_masked(target_rgb):
target_rgb = np.ma.asarray(target_rgb)
target_rgb.mask = np.array((src_mask, src_mask, src_mask))
target_rgb.fill_value = src_fill
profile['count'] = n_bands
# profile['dtype'] = 'uint8'
profile['nodata'] = None
profile['transform'] = guard_transform(profile['transform'])
profile.update(creation_options)
input_base_filepath = os.path.splitext(os.path.abspath(src_path))[0]
dst_path = '{}{}.tif'.format(input_base_filepath, dst_suffix)
logger.info("Writing raster {}".format(dst_path))
with rasterio.open(dst_path, 'w', **profile) as dst:
for b in bixs:
dst.write(target_rgb[b], b + 1)
if src_mask is not None:
gdal_mask = (np.invert(src_mask) * 255).astype('uint8')
# write to extra band
dst.write(gdal_mask, bixs[-1] + 2)
output_paths.append(dst_path)
return output_paths
from rio-hist.
@perrygeo First, thanks for this awesome tool!
Second, I've updated part of this tool to take a list of images, calculates the histogram aggregated over all of the images, and then applies the histogram matching to each individual image so that it matches that average.
This seems related to what you've mentioned in this issue. If it's of interest to you, happy to submit as a PR.
In my test case, it's producing better mosaics then a single reference image for the histogram. 👍
from rio-hist.
@pjbull interesting idea! Mosaics/seams are currently one of the most challenging issue with hist matching so I'd love to check out your solution.
from rio-hist.
I'll clean it up for a PR--it's not super smart, just takes the histogram across the whole collection of images and then follows the same histogram matching algorithm to update each individual image.
I'm using it as part of a machine learning pipeline (rather than for visual inspection) so my tolerance for seams and other artifacts is a little higher. Here's a comparison of processed/unprocessed:
from rio-hist.
This seems like a cool feature!
from rio-hist.
@pjbull I haven't had time to dig into the details yet but this looks great. Thanks! Looking forward to trying it out.
Can you submit a PR with batch_hist_match_worker
function? For now, let's add it to https://github.com/mapbox/rio-hist/blob/master/rio_hist/match.py. I'm not too worried about the repeated code. More important to get it out there for testing - we can factor out common pieces of logic as needed once tests are in place.
from rio-hist.
It looks like this is not going forward. Will close.
from rio-hist.
Related Issues (16)
- Compare to other histogram matching algorithms, benchmark HOT 1
- Handle nodata masks HOT 1
- Which color spaces are useful for histogram matching? HOT 9
- RGBA and masked rasters
- Drop HSV
- Non-exact histogram matching HOT 2
- How to matching the histogram
- LCH space and 16-bit to 8-bit match HOT 1
- Attribute Error HOT 4
- Regarding creation_options HOT 3
- Update for rasterio 1.0-1.1
- 1.0b1 release HOT 1
- 1.0.0 release HOT 2
- For different reference image?
- MemoryError while running on large images
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rio-hist.