Skip to content

Commit f5965fa

Browse files
authored
Merge pull request #2730 from aaronwmorris/dev
Add image sharpening function
2 parents 5958a43 + 8f17591 commit f5965fa

7 files changed

Lines changed: 103 additions & 0 deletions

File tree

indi_allsky/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ class IndiAllSkyConfigBase(object):
119119
"SATURATION_FACTOR_DAY" : 1.0,
120120
"GAMMA_CORRECTION" : 1.0,
121121
"GAMMA_CORRECTION_DAY" : 1.0,
122+
"SHARPEN_AMOUNT" : 0.0,
123+
"SHARPEN_AMOUNT_DAY" : 0.0,
122124
"CCD_COOLING" : False,
123125
"CCD_COOLING_DAY" : False,
124126
"CCD_TEMP" : 15.0,

indi_allsky/flask/forms.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,17 @@ def GAMMA_CORRECTION_validator(form, field):
382382
raise ValidationError('Gamma must be less than 3.0')
383383

384384

385+
def SHARPEN_AMOUNT_validator(form, field):
386+
if not isinstance(field.data, (int, float)):
387+
raise ValidationError('Please enter valid number')
388+
389+
if field.data < 0.0:
390+
raise ValidationError('Sharpen amount must be 0 or greater')
391+
392+
if field.data > 2.0:
393+
raise ValidationError('Sharpen amount must be 2.0 or less')
394+
395+
385396
def SCNR_ALGORITHM_validator(form, field):
386397
if field.data not in list(zip(*form.SCNR_ALGORITHM_choices))[0]:
387398
raise ValidationError('Please select a valid algorithm')
@@ -4311,6 +4322,8 @@ class IndiAllskyConfigForm(FlaskForm):
43114322
SATURATION_FACTOR_DAY = FloatField('Saturation Factor (Day)', validators=[SATURATION_FACTOR_validator], widget=NumberInput(step=0.1))
43124323
GAMMA_CORRECTION = FloatField('Gamma Correction (Night)', validators=[GAMMA_CORRECTION_validator], widget=NumberInput(step=0.1))
43134324
GAMMA_CORRECTION_DAY = FloatField('Gamma Correction (Day)', validators=[GAMMA_CORRECTION_validator], widget=NumberInput(step=0.1))
4325+
SHARPEN_AMOUNT = FloatField('Sharpen Amount (Night)', validators=[SHARPEN_AMOUNT_validator], widget=NumberInput(step=0.1))
4326+
SHARPEN_AMOUNT_DAY = FloatField('Sharpen Amount (Day)', validators=[SHARPEN_AMOUNT_validator], widget=NumberInput(step=0.1))
43144327
CCD_COOLING = BooleanField('CCD Cooling (Night)')
43154328
CCD_COOLING_DAY = BooleanField('CCD Cooling (Day)')
43164329
CCD_TEMP = FloatField('Target CCD Temp (Night)', validators=[CCD_TEMP_validator])
@@ -8959,6 +8972,7 @@ class IndiAllskyImageProcessingForm(FlaskForm):
89598972
WBB_MTF_MIDTONES = FloatField('Blue Balance MTF Midtones', validators=[WB_MTF_MIDTONES_validator], widget=NumberInput(step=0.05))
89608973
SATURATION_FACTOR = FloatField('Saturation Factor', validators=[SATURATION_FACTOR_validator], widget=NumberInput(step=0.1))
89618974
GAMMA_CORRECTION = FloatField('Gamma Correction', validators=[GAMMA_CORRECTION_validator], widget=NumberInput(step=0.1))
8975+
SHARPEN_AMOUNT = FloatField('Sharpen Amount', validators=[SHARPEN_AMOUNT_validator], widget=NumberInput(step=0.1))
89628976
IMAGE_ROTATE = SelectField('Rotate Image', choices=IndiAllskyConfigForm.IMAGE_ROTATE_choices, validators=[IMAGE_ROTATE_validator])
89638977
IMAGE_ROTATE_ANGLE = IntegerField('Rotation Angle', validators=[IMAGE_ROTATE_ANGLE_validator])
89648978
IMAGE_FLIP_V = BooleanField('Flip Image Vertically')

indi_allsky/flask/templates/config.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,17 @@ <h2 class="accordion-header">
18241824
<div class="col-sm-8">1.0 is disabled.</div>
18251825
</div>
18261826

1827+
<div class="form-group row">
1828+
<div class="col-sm-2">
1829+
{{ form_config.SHARPEN_AMOUNT.label(class='col-form-label') }}
1830+
</div>
1831+
<div class="col-sm-2">
1832+
{{ form_config.SHARPEN_AMOUNT(class='form-control bg-secondary') }}
1833+
<div id="SHARPEN_AMOUNT-error" class="invalid-feedback text-danger" style="display: none;"></div>
1834+
</div>
1835+
<div class="col-sm-8">0.0 is disabled. Unsharp mask highpass filter. 0.5 is a good starting point, max 2.0</div>
1836+
</div>
1837+
18271838
<hr>
18281839

18291840
<div class="form-group row">
@@ -1982,6 +1993,17 @@ <h2 class="accordion-header">
19821993
<div class="col-sm-8">1.0 is disabled.</div>
19831994
</div>
19841995

1996+
<div class="form-group row">
1997+
<div class="col-sm-2">
1998+
{{ form_config.SHARPEN_AMOUNT_DAY.label(class='col-form-label') }}
1999+
</div>
2000+
<div class="col-sm-2">
2001+
{{ form_config.SHARPEN_AMOUNT_DAY(class='form-control bg-secondary') }}
2002+
<div id="SHARPEN_AMOUNT_DAY-error" class="invalid-feedback text-danger" style="display: none;"></div>
2003+
</div>
2004+
<div class="col-sm-8">0.0 is disabled. Unsharp mask highpass filter. 0.5 is a good starting point, max 2.0</div>
2005+
</div>
2006+
19852007
<hr>
19862008

19872009
<div class="form-group row">
@@ -9589,6 +9611,8 @@ <h2 class="accordion-header">
95899611
'SATURATION_FACTOR_DAY',
95909612
'GAMMA_CORRECTION',
95919613
'GAMMA_CORRECTION_DAY',
9614+
'SHARPEN_AMOUNT',
9615+
'SHARPEN_AMOUNT_DAY',
95929616
'CFA_PATTERN',
95939617
'SCNR_ALGORITHM',
95949618
'SCNR_ALGORITHM_DAY',
@@ -10590,6 +10614,8 @@ <h2 class="accordion-header">
1059010614
$('#SATURATION_FACTOR_DAY').addClass('bg-dark text-secondary');
1059110615
$('#GAMMA_CORRECTION_DAY').attr({'readonly': true, 'disabled': true});
1059210616
$('#GAMMA_CORRECTION_DAY').addClass('bg-dark text-secondary');
10617+
$('#SHARPEN_AMOUNT_DAY').attr({'readonly': true, 'disabled': true});
10618+
$('#SHARPEN_AMOUNT_DAY').addClass('bg-dark text-secondary');
1059310619
} else {
1059410620
$('#SCNR_ALGORITHM_DAY').removeAttr('readonly disabled');
1059510621
$('#SCNR_ALGORITHM_DAY').removeClass('bg-dark text-secondary');
@@ -10612,6 +10638,8 @@ <h2 class="accordion-header">
1061210638
$('#SATURATION_FACTOR_DAY').removeClass('bg-dark text-secondary');
1061310639
$('#GAMMA_CORRECTION_DAY').removeAttr('readonly disabled');
1061410640
$('#GAMMA_CORRECTION_DAY').removeClass('bg-dark text-secondary');
10641+
$('#SHARPEN_AMOUNT_DAY').removeAttr('readonly disabled');
10642+
$('#SHARPEN_AMOUNT_DAY').removeClass('bg-dark text-secondary');
1061510643
}
1061610644
});
1061710645

@@ -10955,6 +10983,8 @@ <h2 class="accordion-header">
1095510983
$('#SATURATION_FACTOR_DAY').addClass('bg-dark text-secondary');
1095610984
$('#GAMMA_CORRECTION_DAY').attr({'readonly': true, 'disabled': true});
1095710985
$('#GAMMA_CORRECTION_DAY').addClass('bg-dark text-secondary');
10986+
$('#SHARPEN_AMOUNT_DAY').attr({'readonly': true, 'disabled': true});
10987+
$('#SHARPEN_AMOUNT_DAY').addClass('bg-dark text-secondary');
1095810988
}
1095910989

1096010990
// Night timelapse settings

indi_allsky/flask/templates/imageprocessing.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,17 @@
638638
<div class="col-sm-8">1.0 is disabled.</div>
639639
</div>
640640

641+
<div class="form-group row">
642+
<div class="col-sm-2">
643+
{{ form_image_processing.SHARPEN_AMOUNT.label(class='col-form-label') }}
644+
</div>
645+
<div class="col-sm-2">
646+
{{ form_image_processing.SHARPEN_AMOUNT(class='form-control bg-secondary') }}
647+
<div id="SHARPEN_AMOUNT-error" class="invalid-feedback text-danger" style="display: none;"></div>
648+
</div>
649+
<div class="col-sm-8">0.0 is disabled. Unsharp mask highpass filter. 0.5 is a good starting point, max 2.0</div>
650+
</div>
651+
641652
<hr>
642653

643654
<div class="form-group row">
@@ -1990,6 +2001,7 @@ <h5>These options are applied to the unprocessed image automatically</h5>
19902001
'WBB_MTF_MIDTONES',
19912002
'SATURATION_FACTOR',
19922003
'GAMMA_CORRECTION',
2004+
'SHARPEN_AMOUNT',
19932005
'IMAGE_COLORMAP',
19942006
'IMAGE_ROTATE',
19952007
'IMAGE_ROTATE_ANGLE',

indi_allsky/flask/views.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2283,6 +2283,8 @@ def get_context(self):
22832283
'SATURATION_FACTOR_DAY' : self.indi_allsky_config.get('SATURATION_FACTOR_DAY', 1.0),
22842284
'GAMMA_CORRECTION' : self.indi_allsky_config.get('GAMMA_CORRECTION', 1.0),
22852285
'GAMMA_CORRECTION_DAY' : self.indi_allsky_config.get('GAMMA_CORRECTION_DAY', 1.0),
2286+
'SHARPEN_AMOUNT' : self.indi_allsky_config.get('SHARPEN_AMOUNT', 0.0),
2287+
'SHARPEN_AMOUNT_DAY' : self.indi_allsky_config.get('SHARPEN_AMOUNT_DAY', 0.0),
22862288
'CCD_COOLING' : self.indi_allsky_config.get('CCD_COOLING', False),
22872289
'CCD_COOLING_DAY' : self.indi_allsky_config.get('CCD_COOLING_DAY', False),
22882290
'CCD_TEMP' : self.indi_allsky_config.get('CCD_TEMP', 15.0),
@@ -3283,6 +3285,8 @@ def dispatch_request(self):
32833285
self.indi_allsky_config['SATURATION_FACTOR_DAY'] = float(request.json['SATURATION_FACTOR_DAY'])
32843286
self.indi_allsky_config['GAMMA_CORRECTION'] = float(request.json['GAMMA_CORRECTION'])
32853287
self.indi_allsky_config['GAMMA_CORRECTION_DAY'] = float(request.json['GAMMA_CORRECTION_DAY'])
3288+
self.indi_allsky_config['SHARPEN_AMOUNT'] = float(request.json['SHARPEN_AMOUNT'])
3289+
self.indi_allsky_config['SHARPEN_AMOUNT_DAY'] = float(request.json['SHARPEN_AMOUNT_DAY'])
32863290
self.indi_allsky_config['CCD_COOLING'] = bool(request.json['CCD_COOLING'])
32873291
self.indi_allsky_config['CCD_COOLING_DAY'] = bool(request.json['CCD_COOLING_DAY'])
32883292
self.indi_allsky_config['CCD_TEMP'] = float(request.json['CCD_TEMP'])
@@ -7469,6 +7473,7 @@ def get_context(self):
74697473
'WBB_MTF_MIDTONES' : self.indi_allsky_config.get('WBB_MTF_MIDTONES', 0.5),
74707474
'SATURATION_FACTOR' : self.indi_allsky_config.get('SATURATION_FACTOR', 1.0),
74717475
'GAMMA_CORRECTION' : self.indi_allsky_config.get('GAMMA_CORRECTION', 1.0),
7476+
'SHARPEN_AMOUNT' : self.indi_allsky_config.get('SHARPEN_AMOUNT', 0.0),
74727477
'IMAGE_ROTATE' : self.indi_allsky_config.get('IMAGE_ROTATE', ''),
74737478
'IMAGE_ROTATE_ANGLE' : self.indi_allsky_config.get('IMAGE_ROTATE_ANGLE', 0),
74747479
'IMAGE_FLIP_V' : self.indi_allsky_config.get('IMAGE_FLIP_V', True),
@@ -7734,6 +7739,7 @@ def dispatch_request(self):
77347739
p_config['AUTO_WB'] = bool(request.json['AUTO_WB'])
77357740
p_config['SATURATION_FACTOR'] = float(request.json['SATURATION_FACTOR'])
77367741
p_config['GAMMA_CORRECTION'] = float(request.json['GAMMA_CORRECTION'])
7742+
p_config['SHARPEN_AMOUNT'] = float(request.json['SHARPEN_AMOUNT'])
77377743
p_config['IMAGE_ROTATE'] = str(request.json['IMAGE_ROTATE'])
77387744
p_config['IMAGE_ROTATE_ANGLE'] = int(request.json['IMAGE_ROTATE_ANGLE'])
77397745
p_config['IMAGE_FLIP_V'] = bool(request.json['IMAGE_FLIP_V'])

indi_allsky/image.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,10 @@ def processImage(self, i_dict):
652652
self.image_processor.apply_gamma_correction()
653653

654654

655+
# sharpening (unsharp mask)
656+
self.image_processor.sharpen()
657+
658+
655659
if not self.config.get('CONTRAST_ENHANCE_16BIT'):
656660
if not self.night_av[constants.NIGHT_NIGHT] and self.config['DAYTIME_CONTRAST_ENHANCE']:
657661
# Contrast enhancement during the day

indi_allsky/processing.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,41 @@ def _apply_gamma_correction(self, gamma):
21462146
self.image = self._gamma_lut.take(self.image, mode='raise')
21472147

21482148

2149+
def sharpen(self):
2150+
if self.focus_mode:
2151+
# disable processing in focus mode
2152+
return
2153+
2154+
2155+
if self.config.get('USE_NIGHT_COLOR', True):
2156+
SHARPEN_AMOUNT = float(self.config.get('SHARPEN_AMOUNT', 0.0))
2157+
else:
2158+
if self.night_av[constants.NIGHT_NIGHT]:
2159+
# night
2160+
SHARPEN_AMOUNT = float(self.config.get('SHARPEN_AMOUNT', 0.0))
2161+
else:
2162+
# day
2163+
SHARPEN_AMOUNT = float(self.config.get('SHARPEN_AMOUNT_DAY', 0.0))
2164+
2165+
2166+
if SHARPEN_AMOUNT == 0.0:
2167+
# no action
2168+
return
2169+
2170+
2171+
self.image = self._sharpen(SHARPEN_AMOUNT)
2172+
2173+
return True
2174+
2175+
2176+
def _sharpen(self, amount):
2177+
# Unsharp mask: sharpened = original + amount * (original - blurred)
2178+
# GaussianBlur with small kernel extracts low-frequency component;
2179+
# subtracting it amplifies high-frequency detail (edges/stars).
2180+
blurred_image = cv2.GaussianBlur(self.image, (0, 0), sigmaX=2)
2181+
return cv2.addWeighted(self.image, 1.0 + amount, blurred_image, -amount, 0)
2182+
2183+
21492184
def colorize(self):
21502185
if len(self.image.shape) == 3:
21512186
# already color

0 commit comments

Comments
 (0)