Skip to content

Commit

Permalink
Reworked the capture resoltuion situation and added DNG raw support
Browse files Browse the repository at this point in the history
  • Loading branch information
monkeymademe committed Feb 28, 2024
1 parent f7ef4b1 commit 1ef4a31
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 50 deletions.
54 changes: 34 additions & 20 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
from picamera2.encoders import MJPEGEncoder
from picamera2.outputs import FileOutput
from libcamera import Transform, controls

Expand All @@ -33,13 +34,14 @@
selected_resolution = capture_settings["Resolution"]
resolution = capture_settings["available-resolutions"][selected_resolution]
print(capture_settings)
print(resolution)

# Get the sensor modes and pick from the the camera_config
camera_modes = picam2.sensor_modes
mode = picam2.sensor_modes[sensor_mode]

# Create the video_config
video_config = picam2.create_video_configuration(main={'size': mode['size']}, sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
video_config = picam2.create_video_configuration(main={'size':resolution}, sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
print(video_config)

# Pull default settings and filter live_settings for anything picamera2 wont use (because the not all cameras use all settings)
Expand Down Expand Up @@ -137,7 +139,7 @@ def video_feed():
# Route to update settings to the buffer
@app.route('/update_live_settings', methods=['POST'])
def update_settings():
global live_settings, capture_settings, picam2, video_config, resolution, sensor_mode
global live_settings, capture_settings, picam2, video_config, resolution, sensor_mode, mode
try:
# Parse JSON data from the request
data = request.get_json()
Expand All @@ -160,15 +162,21 @@ def update_settings():
capture_settings['Resolution'] = int(data[key])
selected_resolution = int(data[key])
resolution = capture_settings["available-resolutions"][selected_resolution]
return jsonify(success=True, message="Settings updated successfully", settings=live_settings)
stop_camera_stream()
video_config = picam2.create_video_configuration(main={'size':resolution}, sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
start_camera_stream()
return jsonify(success=True, message="Settings updated successfully", settings=capture_settings)
elif key in ('makeRaw'):
capture_settings[key] = data[key]
return jsonify(success=True, message="Settings updated successfully", settings=capture_settings)
elif key == ('sensor_mode'):
sensor_mode = int(data[key])
mode = picam2.sensor_modes[sensor_mode]
stop_camera_stream()
video_config = picam2.create_video_configuration(main={'size': mode['size']}, sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
video_config = picam2.create_video_configuration(main={'size':resolution}, sensor={'output_size': mode['size'], 'bit_depth': mode['bit_depth']})
start_camera_stream()
save_sensor_mode(sensor_mode)
return jsonify(success=True, message="Settings updated successfully", settings=live_settings)
return jsonify(success=True, message="Settings updated successfully", settings=sensor_mode)
except Exception as e:
return jsonify(success=False, message=str(e))

Expand Down Expand Up @@ -269,10 +277,7 @@ def save_sensor_mode(sensor_mode):
####################

def start_camera_stream():
global picam2, output, video_config, still_config
#video_config = picam2.create_video_configuration()
# Flip Camera #################### Make configurable
# video_config["transform"] = Transform(hflip=1, vflip=1)
global picam2, output, video_config
picam2.configure(video_config)
output = StreamingOutput()
picam2.start_recording(JpegEncoder(), FileOutput(output))
Expand All @@ -298,17 +303,18 @@ def take_photo():
global picam2, capture_settings
try:
timestamp = int(datetime.timestamp(datetime.now()))
image_name = f'pimage_{timestamp}.jpg'
image_name = f'pimage_{timestamp}'
filepath = os.path.join(app.config['UPLOAD_FOLDER'], image_name)
request = picam2.capture_request()
request.save("main", filepath)

request.save("main", f'{filepath}.jpg')
if capture_settings["makeRaw"]:
request.save_dng(f'{filepath}.dng')
request.release()
selected_resolution = capture_settings["Resolution"]
resolution = capture_settings["available-resolutions"][selected_resolution]
original_image = Image.open(filepath)
resized_image = original_image.resize(resolution)
resized_image.save(filepath)
#selected_resolution = capture_settings["Resolution"]
#resolution = capture_settings["available-resolutions"][selected_resolution]
#original_image = Image.open(filepath)
#resized_image = original_image.resize(resolution)
#resized_image.save(filepath)
logging.info(f"Image captured successfully. Path: {filepath}")
except Exception as e:
logging.error(f"Error capturing image: {e}")
Expand Down Expand Up @@ -337,24 +343,31 @@ def restart_configure_camera(restart_settings):
# Image Gallery Functions
####################

from datetime import datetime
import os

@app.route('/image_gallery')
def image_gallery():
try:
image_files = [f for f in os.listdir(UPLOAD_FOLDER) if f.endswith(('.jpg', '.jpeg', '.png', '.gif'))]
image_files = [f for f in os.listdir(UPLOAD_FOLDER) if f.endswith(('.jpg'))]

if not image_files:
# Handle the case where there are no files
return render_template('no_files.html')

# Create a list of dictionaries containing file name and timestamp
# Create a list of dictionaries containing file name, timestamp, and dng presence
files_and_timestamps = []
for image_file in image_files:
# Extracting Unix timestamp from the filename
unix_timestamp = int(image_file.split('_')[-1].split('.')[0])
timestamp = datetime.utcfromtimestamp(unix_timestamp).strftime('%Y-%m-%d %H:%M:%S')

# Check if corresponding .dng file exists
dng_file = os.path.splitext(image_file)[0] + '.dng'
has_dng = os.path.exists(os.path.join(UPLOAD_FOLDER, dng_file))

# Appending dictionary to the list
files_and_timestamps.append({'filename': image_file, 'timestamp': timestamp})
files_and_timestamps.append({'filename': image_file, 'timestamp': timestamp, 'has_dng': has_dng, 'dng_file': dng_file})

# Sorting the list based on Unix timestamp
files_and_timestamps.sort(key=lambda x: x['timestamp'], reverse=True)
Expand All @@ -364,6 +377,7 @@ def image_gallery():
logging.error(f"Error loading image gallery: {e}")
return render_template('error.html', error=str(e))


@app.route('/delete_image/<filename>', methods=['DELETE'])
def delete_image(filename):
try:
Expand Down
2 changes: 2 additions & 0 deletions camera-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
},
"sensor-mode": 2,
"capture-settings": {
"Resize": false,
"makeRaw": true,
"Resolution": 0,
"available-resolutions": [
[
Expand Down
Binary file added static/gallery/pimage_1709139459.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions templates/camera_info.html
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,7 @@ <h2 class="fw-bold text-body-emphasis">{{ connected_camera_data.module_name }}</
{% endif %}
</div>
</div>







<script>
Expand Down
50 changes: 38 additions & 12 deletions templates/camerasettings.html
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ <h2 class="accordion-header">
</div>
</div>
</div>
<!-- ###### Auto White Balance Settings ###### -->
<!-- ###### Camera Capture Settings ###### -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSix" aria-expanded="false" aria-controls="collapseSix">
Expand All @@ -406,27 +406,33 @@ <h2 class="accordion-header">
<div id="collapseSix" class="accordion-collapse collapse" data-bs-parent="#accordionExample">
<div class="accordion-body">
<!-- Resolution -->
<p>Select Resolution:</p>
<p>Select Feed/Capture Resolution:</p>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution0" onclick="adjustCheckboxSetting('Resolution', '0')">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution0" onclick="adjustResoltuion('Resolution', '0')">
<label class="form-check-label" for="Resolution0">4608, 2592</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution1" onclick="adjustCheckboxSetting('Resolution', '1')">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution1" onclick="adjustResoltuion('Resolution', '1')">
<label class="form-check-label" for="Resolution1">2304, 1296</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution2" onclick="adjustCheckboxSetting('Resolution', '2')">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution2" onclick="adjustResoltuion('Resolution', '2')">
<label class="form-check-label" for="Resolution2">1920, 1080</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution3" onclick="adjustCheckboxSetting('Resolution', '3')">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution3" onclick="adjustResoltuion('Resolution', '3')">
<label class="form-check-label" for="Resolution3">1280, 720</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution4" onclick="adjustCheckboxSetting('Resolution', '4')">
<input class="form-check-input" type="checkbox" name="Resolution" id="Resolution4" onclick="adjustResoltuion('Resolution', '4')">
<label class="form-check-label" for="Resolution4">640, 360</label>
</div>
<hr></hr>
<!-- Make Raw -->
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="makeRaw" onchange="adjustSwitchSetting('makeRaw')">
<label for="makeRaw" >Enable: Save Raw Image</label>
</div>
<!-- End of Line-->
</div>
</div>
Expand Down Expand Up @@ -486,7 +492,7 @@ <h2 class="accordion-header">
});

const checkboxSettings = ['AfMode', 'AfRange', 'AfSpeed', 'AeConstraintMode', 'AeExposureMode', 'AeFlickerMode', 'AeMeteringMode', 'AwbMode', 'Resolution'];
const switchSettings = ['AwbEnable', 'AeEnable', 'AwbMode', 'hflip', 'vflip'];
const switchSettings = ['AwbEnable', 'AeEnable', 'AwbMode', 'hflip', 'vflip', 'makeRaw'];
const sliderSettings = ['LensPosition', 'ExposureValue', 'Brightness', 'Contrast', 'Saturation', 'Sharpness'];
const inputSettings = ['ExposureTime', 'AeFlickerPeriod']

Expand Down Expand Up @@ -661,12 +667,16 @@ <h2 class="accordion-header">
function adjustCheckboxSetting(settingId, selection) {
// Convert settingValue to an integer
const settingValue = parseInt(selection);

// Update UI with the new value
updateUI({ [settingId]: settingValue });
// Update server settings
updateLiveSettings({ [settingId]: settingValue });
// Notify console
console.log(settingId, 'changed:', settingValue);

// Update server settings and return the promise
return updateLiveSettings({ [settingId]: settingValue })
.then(() => {
// Notify console
console.log(settingId, 'changed:', settingValue);
});
}

function adjustSliderSetting(settingId, selection) {
Expand Down Expand Up @@ -880,6 +890,22 @@ <h2 class="accordion-header">
}
}

function adjustResoltuion(settingId, selection) {
// Call adjustCheckboxSetting
adjustCheckboxSetting(settingId, selection)
.then(() => {
// The asynchronous part is done, now you can perform other tasks
refreshCameraFeed();
})
.catch(error => {
console.error('Error updating server settings:', error);
// Handle the error if needed
});
}




function adjustflip(mode) {
// Convert mode to an integer
const hflipswitch = document.getElementById('hflip');
Expand Down
31 changes: 17 additions & 14 deletions templates/image_gallery.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,26 @@ <h5 class="modal-title" id="deleteConfirmationModalLabel">Confirm Deletion</h5>
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
{% for file_data in image_files %}
<div class="col" id="card_{{ file_data['filename'] }}">
<div class="card shadow-sm" >
<img src="{{ url_for('static', filename='gallery/' + file_data['filename']) }}" alt="{{ file_data['filename'] }}" class="bd-placeholder-img card-img-top" width="100%">
<div class="card-body">
<p class="card-text">Date taken: {{ file_data['timestamp'] }}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.location.href='/view_image/{{ file_data['filename'] }}'">View</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="openDeleteConfirmationModal('{{ file_data['filename'] }}', 'card_{{ file_data['filename'] }}')">Delete</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.location.href='/download_image/{{ file_data['filename'] }}'">Download</button>

</div>
</div>
<div class="col" id="card_{{ file_data['filename'] }}">
<div class="card shadow-sm" >
<img src="{{ url_for('static', filename='gallery/' + file_data['filename']) }}" alt="{{ file_data['filename'] }}" class="bd-placeholder-img card-img-top" width="100%">
<div class="card-body">
<p class="card-text">Date taken: {{ file_data['timestamp'] }}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.location.href='/view_image/{{ file_data['filename'] }}'">View</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="openDeleteConfirmationModal('{{ file_data['filename'] }}', 'card_{{ file_data['filename'] }}')">Delete</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.location.href='/download_image/{{ file_data['filename'] }}'">Download</button>
{% if file_data['has_dng'] %}
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.location.href='/download_image/{{ file_data['dng_file'] }}'">Download Raw</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}

</div>
</div>
</div>
Expand Down

0 comments on commit 1ef4a31

Please sign in to comment.