Skip to content

Commit

Permalink
Merge pull request #1001 from aaronwmorris/dev
Browse files Browse the repository at this point in the history
BREAKING CHANGE:  Detection masks utilized prior to geometry changes
  • Loading branch information
aaronwmorris authored Oct 25, 2023
2 parents 3ac45eb + a65236d commit 0b40678
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 18 deletions.
3 changes: 3 additions & 0 deletions indi_allsky/flask/static/svg/layers.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions indi_allsky/flask/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
<a href="{{ url_for('indi_allsky.image_processing_view') }}" class="dropdown-item">
<img src="{{ url_for('indi_allsky.static', filename='svg/magic.svg') }}" width="16" height="16" alt="Log"><span class="ms-1 d-none d-sm-inline">Process FITS</span> </a>
</li>
<li>
<a href="{{ url_for('indi_allsky.mask_view') }}" class="dropdown-item">
<img src="{{ url_for('indi_allsky.static', filename='svg/layers.svg') }}" width="16" height="16" alt="Log"><span class="ms-1 d-none d-sm-inline">Mask Base</span> </a>
</li>
<li>
<a href="{{ url_for('indi_allsky.log_view') }}" class="dropdown-item">
<img src="{{ url_for('indi_allsky.static', filename='svg/terminal-fill.svg') }}" width="16" height="16" alt="Log"><span class="ms-1 d-none d-sm-inline">Log</span> </a>
Expand Down
67 changes: 67 additions & 0 deletions indi_allsky/flask/templates/mask.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{% extends 'base.html' %}

{% block title %}indi-allsky: Mask Base{% endblock %}

{% block head %}
<meta charset="UTF-8">
<style>
#mask_img {
width: 100%;
height: auto;
}
</style>
<script>
var url = '{{ mask_image_uri }}';
</script>
{% endblock %}

{% block content %}
<div class="row">
<div class="col-12 text-center" style="font-size:10px">
This image is the original camera output. It is not rotated, flipped, or cropped.
</div>
</div>

<div class="row">
<div class="col-1"></div>
<div class="col-10">
<img id="mask_img">
</div>
</div>

<div class="row">
<div class="col-12 text-center" style="font-size:10px">
<span>Generated: {{ mask_date }}</span>
</div>
</div>

<div class="row">
<div class="col-12 text-center">
<span id="mask_download"></span>
</div>
</div>
<script>
function init() {
$('#mask_img').attr('src', url + '?' + new Date().getTime());

$('#mask_download').html(
$('<a />', {
'href' : $('#mask_img').attr('src'),
'rel' : 'noopener noreferrer',
'download' : 'mask_base.png',
}).html(
$('<span />', {
'text' : 'Download Mask Base',
'class' : "badge pill bg-info text-dark",
})
)
);
}

$( document ).ready(function() {
init();
});

</script>

{% endblock %}
24 changes: 24 additions & 0 deletions indi_allsky/flask/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,29 @@ def dispatch_request(self):
return redirect(url_for('indi_allsky.index_view'))


class MaskView(TemplateView):
def get_context(self):
context = super(MaskView, self).get_context()

mask_image_uri = Path('images/mask_base.png')

context['mask_image_uri'] = str(mask_image_uri)


image_dir = Path(self.indi_allsky_config['IMAGE_FOLDER']).absolute()
mask_image_p = image_dir.joinpath(mask_image_uri.name)

if mask_image_p.exists():
mask_mtime = mask_image_p.stat().st_mtime
mask_mtime_dt = datetime.fromtimestamp(mask_mtime)
context['mask_date'] = mask_mtime_dt.strftime('%Y-%m-%d %H:%M:%S')
else:
context['mask_date'] = ''


return context


class CamerasView(TemplateView):
def get_context(self):
context = super(CamerasView, self).get_context()
Expand Down Expand Up @@ -4731,6 +4754,7 @@ def images_folder(path):
bp_allsky.add_url_rule('/darks', view_func=DarkFramesView.as_view('darks_view', template_name='darks.html'))
bp_allsky.add_url_rule('/processing', view_func=ImageProcessingView.as_view('image_processing_view', template_name='imageprocessing.html'))
bp_allsky.add_url_rule('/js/processing', view_func=JsonImageProcessingView.as_view('js_image_processing_view'))
bp_allsky.add_url_rule('/mask', view_func=MaskView.as_view('mask_view', template_name='mask.html'))

bp_allsky.add_url_rule('/public', view_func=PublicIndexView.as_view('public_index_view')) # redirect

Expand Down
69 changes: 54 additions & 15 deletions indi_allsky/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def __init__(
self.name = 'Image-{0:d}'.format(idx)

self.config = config

self.error_q = error_q
self.image_q = image_q
self.upload_q = upload_q
Expand Down Expand Up @@ -140,12 +141,15 @@ def __init__(

self.filename_t = 'ccd{0:d}_{1:s}.{2:s}'

self.generate_mask_base = True

self.target_adu_found = False
self.current_adu_target = 0
self.hist_adu = []

self.sqm_value = 0

self.image_count = 0
self.metadata_count = 0

self.image_processor = ImageProcessor(
Expand Down Expand Up @@ -340,6 +344,9 @@ def processImage(self, i_dict):
filename_p.unlink() # original file is no longer needed


self.image_count += 1


# use original value if not defined
libcamera_black_level = image_data.get('libcamera_black_level', libcamera_black_level)

Expand Down Expand Up @@ -494,6 +501,28 @@ def processImage(self, i_dict):
adu, adu_average = self.calculate_exposure(adu, exposure)


# generate a new mask base once the target ADU is found
# this should only only fire once per restart
if self.generate_mask_base and self.target_adu_found:
self.generate_mask_base = False
self.write_mask_base_img(self.image_processor.image)


# line detection
if self.night_v.value and self.config.get('DETECT_METEORS'):
self.image_processor.detectLines()


# star detection
if self.night_v.value and self.config.get('DETECT_STARS', True):
self.image_processor.detectStars()


# additional draw code
if self.config.get('DETECT_DRAW'):
self.image_processor.drawDetections()


if self.config.get('IMAGE_ROTATE'):
self.image_processor.rotate_90()

Expand All @@ -512,21 +541,6 @@ def processImage(self, i_dict):
self.image_processor.flip_h()


# line detection
if self.night_v.value and self.config.get('DETECT_METEORS'):
self.image_processor.detectLines()


# star detection
if self.night_v.value and self.config.get('DETECT_STARS', True):
self.image_processor.detectStars()


# additional draw code
if self.config.get('DETECT_DRAW'):
self.image_processor.drawDetections()


# crop
if self.config.get('IMAGE_CROP_ROI'):
self.image_processor.crop_image()
Expand Down Expand Up @@ -1127,6 +1141,31 @@ def export_raw_image(self, i_ref, jpeg_exif=None):
self._miscUpload.s3_upload_raw(raw_entry, raw_metadata)


def write_mask_base_img(self, data):
logger.info('Generating new mask base')
f_tmpfile = tempfile.NamedTemporaryFile(mode='w+b', delete=False, suffix='.png')
f_tmpfile.close()

tmpfile_name = Path(f_tmpfile.name)


cv2.imwrite(str(tmpfile_name), data, [cv2.IMWRITE_PNG_COMPRESSION, self.config['IMAGE_FILE_COMPRESSION']['png']])

mask_file = self.image_dir.joinpath('mask_base.png')

try:
mask_file.unlink()
except FileNotFoundError:
pass


shutil.copy2(str(tmpfile_name), str(mask_file))
mask_file.chmod(0o644)


tmpfile_name.unlink()


def write_img(self, data, i_ref, camera, jpeg_exif=None):
f_tmpfile = tempfile.NamedTemporaryFile(mode='w+b', delete=False, suffix='.{0}'.format(self.config['IMAGE_FILE_TYPE']))
f_tmpfile.close()
Expand Down
113 changes: 113 additions & 0 deletions indi_allsky/maskProcessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
### This is a mini image processor for masks
### Masks are pre-rotation/flip/cropping and need these operations to be applied to processed images

#import time
import cv2
import logging


logger = logging.getLogger('indi_allsky')


class MaskProcessor(object):
def __init__(
self,
config,
bin_v,
):

self.config = config
self.bin_v = bin_v

self._image = None


@property
def image(self):
return self._image

@image.setter
def image(self, new_image):
self._image = new_image


def rotate_90(self):
try:
rotate_enum = getattr(cv2, self.config['IMAGE_ROTATE'])
except AttributeError:
logger.error('Unknown rotation option: %s', self.config['IMAGE_ROTATE'])
return

self.image = cv2.rotate(self.image, rotate_enum)


def rotate_angle(self):
angle = self.config.get('IMAGE_ROTATE_ANGLE')

#rotate_start = time.time()

height, width = self.image.shape[:2]
center_x = int(width / 2)
center_y = int(height / 2)

rot = cv2.getRotationMatrix2D((center_x, center_y), int(angle), 1.0)

abs_cos = abs(rot[0, 0])
abs_sin = abs(rot[0, 1])

bound_w = int(height * abs_sin + width * abs_cos)
bound_h = int(height * abs_cos + width * abs_sin)

rot[0, 2] += bound_w / 2 - center_x
rot[1, 2] += bound_h / 2 - center_y

self.image = cv2.warpAffine(self.image, rot, (bound_w, bound_h))

rot_height, rot_width = self.image.shape[:2]
mod_height = rot_height % 2
mod_width = rot_width % 2

if mod_height or mod_width:
# width and height needs to be divisible by 2 for timelapse
crop_height = rot_height - mod_height
crop_width = rot_width - mod_width

self.image = self.image[
0:crop_height,
0:crop_width,
]


#processing_elapsed_s = time.time() - rotate_start
#logger.warning('Rotation in %0.4f s', processing_elapsed_s)


def flip(self, cv2_axis):
self.image = cv2.flip(self.image, cv2_axis)


def flip_v(self):
self.flip(0)


def flip_h(self):
self.flip(1)


def crop_image(self):
# divide the coordinates by binning value
x1 = int(self.config['IMAGE_CROP_ROI'][0] / self.bin_v.value)
y1 = int(self.config['IMAGE_CROP_ROI'][1] / self.bin_v.value)
x2 = int(self.config['IMAGE_CROP_ROI'][2] / self.bin_v.value)
y2 = int(self.config['IMAGE_CROP_ROI'][3] / self.bin_v.value)


self.image = self.image[
y1:y2,
x1:x2,
]

#new_height, new_width = self.image.shape[:2]
#logger.info('New cropped size: %d x %d', new_width, new_height)


2 changes: 1 addition & 1 deletion indi_allsky/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "7.5"
__version__ = "7.6"
__config_level__ = "20231018.0"

Loading

0 comments on commit 0b40678

Please sign in to comment.