mirror of
https://github.com/EyeTrackVR/EyeTrackVR-Docs.git
synced 2025-09-26 23:09:15 +08:00
update - full refactor
This commit is contained in:
parent
c08619e9ca
commit
861e1e994d
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"loucass",
|
||||
"pcvr",
|
||||
"photobiological"
|
||||
]
|
||||
}
|
67
README.md
67
README.md
@ -1,40 +1,69 @@
|
||||
[](https://github.com/RedHawk989/EyeTrackVR/issues) [](https://github.com/RedHawk989/EyeTrackVR/network) [](https://github.com/RedHawk989/EyeTrackVR/stargazers)
|
||||
|
||||
- [EyeTrackVR](#eyetrackvr)
|
||||
- [This project is in development and is not fully finished](#this-project-is-in-development-and-is-not-fully-finished)
|
||||
- [Hardware](#hardware)
|
||||
- [About IR Emitter Safety](#about-ir-emitter-safety)
|
||||
- [Make sure you are using NON-focused emmiters and at around 5ma total power](#make-sure-you-are-using-non-focused-emmiters-and-at-around-5ma-total-power)
|
||||
- [Firmware](#firmware)
|
||||
- [Headset support](#headset-support)
|
||||
- [Contact](#contact)
|
||||
- [Licenses](#licenses)
|
||||
|
||||
# EyeTrackVR
|
||||
Open source and affordable VR eye tracker platform for VR Chat via OSC
|
||||
#### This project is in development and is not fully finished.
|
||||
|
||||
Open source and *affordable* VR eye tracker platform for [VRChat](https://hello.vrchat.com/) via `OSC` and `UDP` protocol.
|
||||
|
||||
#### This project is in development and is not fully finished
|
||||
|
||||
## Hardware
|
||||
3d files for mounting brackets will be found here https://github.com/RedHawk989/EyeTrackVR-Hardware
|
||||
IR emmitter files will also be located there.
|
||||
|
||||
Hardware will hopefully be a esp cam 32 with a 160fov ir camera this is not confirmed and very likely could change
|
||||
3d files for mounting brackets will be found [here](https://github.com/RedHawk989/EyeTrackVR-Hardware)
|
||||
IR emitter files will also be located there.
|
||||
|
||||
## ABOUT IR EMMITTER SAFETY
|
||||
Please exercise extreme caution when messing around with IR emmitters.
|
||||
Once safety testing has been compleated links and files will be provided for the emmitters. Please do not try to make, or use any emmitters unless you know exactly what you are doing as it could be very harmful for your eyes if not done correctly.
|
||||
When files and reasorces are released DO NOT BYPASS OR NOT DO ANY SAFTY FEATURES PUT IN PLACE. This can result in a very harmful outcome.
|
||||
The saftey measures were put in place to REDUCE the potential falure risk. All further safety responsibilities is on the user.
|
||||
Hardware will hopefully be a `ESP32-Cam` with a `160fov ir camera`. ***This is not confirmed and very likely could change***.
|
||||
|
||||
## About IR Emitter Safety
|
||||
|
||||
Please *exercise extreme caution* when messing around with IR emitters.
|
||||
<u style="color:#ff0e01;">Once safety testing has been completed links and files will be provided for the emitters</u>. Please do not try to make, or use any emitters unless you know exactly what you are doing as it could be very harmful for your eyes if not done correctly.
|
||||
When files and resources are released <u style="color:#ff0e01;">**DO NOT BYPASS (OR NOT DO) ANY SAFETY FEATURES PUT IN PLACE**</u>. This can result in irreversible bodily harm.
|
||||
The safety measures were put in place to REDUCE the potential failure risk. All further safety responsibilities is on the user.
|
||||
This includes visually checking with an IR camera that the brightness is correct.
|
||||
|
||||
**Make sure you are using NON-focused emmiters and at around 5ma total power**
|
||||
##### Make sure you are using NON-focused emmiters and at around 5ma total power
|
||||
|
||||
https://www.ncbi.nlm.nih.gov/labs/pmc/articles/PMC3116568/
|
||||
[Effect of infrared radiation on the lens](./docs/Reference_Docs/saftey/effect_of_ir_on_the_lens.pdf)
|
||||
|
||||
https://www.osha.gov/sites/default/files/training-library_nir_stds_20021011.ppt
|
||||
[Training-library Nir Stds](./docs/Reference_Docs/saftey/training-library_nir_stds_20021011.ppt)
|
||||
|
||||
https://dammedia.osram.info/media/bin/osram-dam-2496608/AN002_Details%20on%20photobiological%20safety%20of%20LED%20light%20sources.pdf
|
||||
[AN002_Details on photobiological safety of LED light sources](./docs/Reference_Docs/saftey/AN002_Details_on_photobiological_safety_of_LED_light_sources.pdf)
|
||||
|
||||
## Firmware
|
||||
Current testing has been with loucass003's firmware found here https://github.com/Futurabeast/futura-face-cam
|
||||
There has been work for a different firmware by a community member but that has not been tested by me here https://github.com/lorow/OpenIris
|
||||
|
||||
Current testing has been with `loucass003's` firmware found [here](https://github.com/Futurabeast/futura-face-cam).
|
||||
There has been work for a different firmware by a community member but that ***has not been tested by me*** [here](https://github.com/lorow/OpenIris).
|
||||
|
||||
## Headset support
|
||||
Initial support will be Quest 2 (pcvr)
|
||||
|
||||
Initial support will be Quest 2 (pcvr)
|
||||
Support for other headsets from the community.
|
||||
|
||||
#### Contact
|
||||
#### Contact
|
||||
|
||||
Please join our discord for updates and any questions.
|
||||
|
||||
https://discord.gg/kkXYbVykZX
|
||||
[](https://discord.gg/kkXYbVykZX)
|
||||
|
||||
# Licenses
|
||||
|
||||
[](https://github.com/RedHawk989/EyeTrackVR/blob/master/LICENSE)
|
||||
|
||||
***All software is under the MIT license.
|
||||
All documentation, including the Wiki, is under the Creative Commons CC-BY-SA-4.0 license***.
|
||||
|
||||
<!-- <div align="center">
|
||||
<img src="./docs/assets/images/licenses/licenses.svg" width="300" alt="Open Licenses" />
|
||||
</div> -->
|
||||
|
||||
[Top](#eyetrackvr)
|
||||
|
94
docs/README.md
Normal file
94
docs/README.md
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
layout: default
|
||||
title: Summary
|
||||
nav_order: 1
|
||||
has_children: false
|
||||
---
|
||||
|
||||
# Summary
|
||||
|
||||
Welcome to the documentation for the Cyan Decarbonizer version 1, a DIY small-scale open hardware direct air carbon capture (DACC) system.
|
||||
|
||||
|  |
|
||||
|:--:|
|
||||
| *The outer box and fan are optional for experimentation. The humidification chamber and air pump are the minimum required items.* |
|
||||
|
||||
## Purpose
|
||||
|
||||
This portable system operates at a small enough scale to empower individuals to tangibly experience carbon removal. It is also designed to get people actively thinking and sharing their carbon footprint and their role in the carbon cycle.
|
||||
|
||||
You can take a 5-minute tour of a Cyan system and learn more about the details of the project here:
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.youtube.com/watch?v=yOzDFJZBM_w&t=2s"><img src="./assets/images/assemblyinst/cyan_blk_bg.png" width="400" alt="Cyan version 1" /></a>
|
||||
</div>
|
||||
|
||||
If you have twenty-something minutes, details about the device design, current challenges, opportunities and big questions, and the Cyan mission's general goals and aspirations are available here:
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.youtube.com/watch?v=98940CgZHI0" target="_blank"><img src="./assets/images/vtEUvj4.jpeg" width="400" alt="TSM: Hello, Cyan! - with Dahl Winters" /></a>
|
||||
</div>
|
||||
|
||||
# Vision
|
||||
|
||||
Cyan is named in honor of `cyanobacteria`. As cyanobacteria did with oxygen, the vision was that many of these small-scale DACC units might come to have a significant impact on atmospheric CO2 and thus climate change. If not directly due to their small size, then indirectly through the promotion of DACC as a technology and through the educational experience of using Cyan.
|
||||
|
||||
The educational experience might be the biggest way that Cyan units will combat climate change. One can quickly see what CO2 emissions look like in trapped form, how much it weighs, and how long it takes to capture it. DACC starts to become a conversation point among friends and family. People start wanting to run Cyans to erase the emissions from their light bulbs, for example. And if they can neutralize those emissions, what’s next? Household emissions? Should I buy a bigger DACC unit to cover my home’s emissions? And so on.
|
||||
|
||||
# The Documentation, Q&A, Wiki, and Project Updates
|
||||
|
||||
The [Cyan Assembly Instructions](./Reference_Docs/assembly/Cyan_Assembly_Instructions.pdf) will get you started with an overview of how it works, a parts list, and instructions for putting it together. All this information is also found on our [Wiki](https://github.com/openair-collective/openair-cyan/wiki).
|
||||
|
||||
We now have a [Cyan Q&A](./Reference_Docs/extras/Cyan_Q_and_A.md) that goes over some common general and technical questions.
|
||||
|
||||
The [Supporting System Calculations](./Reference_Docs/data/Cyan%20-%20Supporting%20System%20Calculations.xlsx) describe the logic that went into designing the system, from a LCA/TEA perspective.
|
||||
|
||||
The [CO2 Capture Measurements](./Reference_Docs/data/Cyan%20-%20CO2%20Capture%20Measurements.xlsx) describe results obtained so far on how well this system can perform.
|
||||
|
||||
To report your `modifications`, `experimental results`, and `feedback` for making Cyan better, please visit the [Cyan DataShare](https://sites.google.com/view/cyan-datashare/) site.
|
||||
|
||||
To see an overview of where we are with current progress, please view the [Projects board](https://github.com/openair-collective/openair-cyan/projects/1).
|
||||
|
||||
Lastly, to view Cyan Project Progress updates on the OpenAir Forum, those are [available here](https://www.openairforum.org/t/cyan-project-progress/105/14).
|
||||
|
||||
# Carbon Capture Details
|
||||
|
||||
<div align="center">
|
||||
<img src="./assets/images/assemblyinst/Cyan_Process.jpg" width="600" alt="Diagram of Cyan's Carbon Removal Process" />
|
||||
</div>
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
The input material, `calcium hydroxide`, can be found in any home improvement store as **`hydrated lime`**. However, for Cyan to be *carbon-negative* the input must either be sourced from a low-carbon supplier or obtained from waste cement.
|
||||
|
||||
The output material, `calcium carbonate` can be used in building materials (cement-lime) or as **`agricultural lime`**.
|
||||
|
||||
***`Magnesium hydroxide`*** is also an option for use as the input material. Though perhaps *more expensive* depending on the source, it is less alkaline than calcium hydroxide and has performed as well to slightly better than calcium hydroxide in carbon capture effectiveness in our Cyan tests. It can also be used to make tiles or blocks when mixed with cement and optionally biochar. We hope to have a low-carbon supplier later this year.
|
||||
|
||||
## Emissions Reduction
|
||||
|
||||
At present, a Cyan will remove over the course of `24 hours` the *same amount of CO2* emitted by running a 9-watt LED bulb for 3 hours. That is `2 grams of CO2 for 10 grams of input material` - or a 0.2:1 ratio.
|
||||
|
||||
*It is within theoretical possibility for this unit to remove the emissions of 20 9-watt LED bulbs for 3 hours (a whole house at night), that is 0.382 kg of CO2 that would need to be captured. Adequate surface area needs to be provided within the unit to do this and we are presently working on that*.
|
||||
|
||||
## Achieving Negative Emissions
|
||||
|
||||
The calcium hydroxide needs to be exposed to high humidity from between `3 to 16 hours` to achieve a good level of carbonation. Keeping the electricity consumption low will ensure Cyan removes more CO2 than it emits through electricity use.
|
||||
|
||||
`Cyan uses 1.5 watts of electricity` to power the air pump which bubbles air through water and humidifies the calcium hydroxide, allowing CO2 to be absorbed. However, the air pump *does not* need to be run continuously to maintain adequate humidification. A duty cycle of, say, ***10 minutes on & 50 minutes off*** is possible and suggested for negative emissions (more CO2 removed than consumed by the air pump).
|
||||
|
||||
The fan uses `3 watts` but only needs to be run intermittently to refresh the air inside the box. The fan is also useful to speed up dry time but this is *not essential for operation*. If you're going for negative emissions, air drying would really help unless you are using renewable energy to power your unit.
|
||||
|
||||
# Licenses
|
||||
|
||||
[](https://github.com/openair-collective/openair-cyan/blob/master/LICENSE)
|
||||
|
||||
***All hardware materials and designs provided here are licensed under the [CERN-OHL-P](https://opensource.org/CERN-OHL-P) hardware license.
|
||||
All software is under the GNU General Public License v3.0 (GPL-3.0).
|
||||
All documentation, including the Wiki, is under the Creative Commons CC-BY-SA-4.0 license***.
|
||||
|
||||
<div align="center">
|
||||
<img src="./assets/images/licenses/licenses.svg" width="300" alt="Open Licenses" />
|
||||
</div>
|
||||
|
||||
[Top](#summary)
|
Binary file not shown.
BIN
docs/Reference_Docs/saftey/effect_of_ir_on_the_lens.pdf
Normal file
BIN
docs/Reference_Docs/saftey/effect_of_ir_on_the_lens.pdf
Normal file
Binary file not shown.
Binary file not shown.
38
docs/_config.yml
Normal file
38
docs/_config.yml
Normal file
@ -0,0 +1,38 @@
|
||||
title: 'Open Air Cyan Documentation'
|
||||
remote_theme: just-the-docs/just-the-docs
|
||||
color_scheme: 'light'
|
||||
search_enabled: true
|
||||
logo: './assets/images/openair-logo.png'
|
||||
heading_level: 6
|
||||
previews: 3
|
||||
preview_words_before: 5
|
||||
preview_words_after: 10
|
||||
tokenizer_separator: /[\s/]+/
|
||||
rel_url: true
|
||||
button: true
|
||||
aux_links:
|
||||
'Documentation':
|
||||
- '//github.com/openair-collective/openair-cyan/tree/main/docs/Reference_Docs'
|
||||
'Code':
|
||||
- '//github.com/openair-collective/openair-cyan/tree/dev/Code'
|
||||
aux_links_new_tab: false
|
||||
heading_anchors: true
|
||||
gh_edit_link: true # show or hide edit this page link
|
||||
gh_edit_link_text: 'Edit this page on GitHub.'
|
||||
gh_edit_repository: 'https://github.com/openair-collective/openair-cyan/' # the github URL for your repo
|
||||
gh_edit_branch: 'docs' # the branch that your docs is served from
|
||||
gh_edit_source: docs # the source that your files originate from
|
||||
gh_edit_view_mode: 'edit' # "tree" or "edit" if you want the user to jump into the editor immediately
|
||||
ga_tracking: UA-5555555-55 # Google Analytics tracking code - Dahl Winters please update this to your own code
|
||||
ga_tracking_anonymize_ip: true # Use GDPR compliant Google Analytics settings (true by default)
|
||||
|
||||
# Google Forms configuration
|
||||
# comment-post: https://docs.google.com/forms/d/e/1FAIpQLScuqqkKDIUbPJpGfyLQJQe3mBNvQh45-eryzMWbqm7k7ALjGA
|
||||
# comment-read: https://docs.google.com/spreadsheets/d/1szz__qSlPZ5LmDlR9ovNqdSCOgoUAYCWg-gWwkCQlmw/
|
||||
# comment-post-fields: [entry.1141065141, entry.484163683, entry.231340692]
|
||||
google_forms_comments:
|
||||
google_app_script: https://script.google.com/macros/s/AKfycbwnErJEKIMcwQ5tlNS9rZ1KxHkhkJGkR9BNvKfkaz4X7MrZzc42VKN4ehzQ9AhfGChJ/exec
|
||||
recaptcha_site_key: 6LfKJsMfAAAAAFLjDTY0PG9RdcpLBuiuoFNyQLsh
|
||||
chunked_comment_loading: true
|
||||
lazy_load_comments: true
|
||||
comment_replies_enabled: true
|
968
docs/_includes/comments.html
Normal file
968
docs/_includes/comments.html
Normal file
@ -0,0 +1,968 @@
|
||||
{%- comment -%}
|
||||
This section is to determine whether or not chunked comment loading is enabled.
|
||||
By default this is turned ON. If the page has enabled or disabled this feature explicitly,
|
||||
the individual page value is used. Otherwise the site's value, if any, is used.
|
||||
{%- endcomment -%}
|
||||
{%- assign chunked_loading_enabled = true -%}
|
||||
{%- if page.chunked_comment_loading == false -%}
|
||||
{%- assign chunked_loading_enabled = false -%}
|
||||
{%- else -%}
|
||||
{%- if site.google_forms_comments.chunked_comment_loading == false and page.chunked_comment_loading != true -%}
|
||||
{%- assign chunked_loading_enabled = false -%}
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%}
|
||||
This section is to determine whether or not lazy loading of the comments is enabled.
|
||||
By default this is turned OFF. If the page has enabled or disabled this feature explicitly,
|
||||
the individual page value is used. Otherwise the site's value, if any, is used.
|
||||
{%- endcomment -%}
|
||||
{%- assign lazy_load_enabled = false -%}
|
||||
{%- if page.lazy_load_comments == true -%}
|
||||
{%- assign lazy_load_enabled = true -%}
|
||||
{%- elsif site.google_forms_comments.lazy_load_comments == true -%}
|
||||
{%- unless page.lazy_load_comments == false -%}
|
||||
{%- assign lazy_load_enabled = true -%}
|
||||
{%- endunless -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%}
|
||||
This section is to determine whether or recaptcha is enabled.
|
||||
{%- endcomment -%}
|
||||
{%- assign recaptcha_enabled = false -%}
|
||||
{%- if site.google_forms_comments.recaptcha_site_key -%}
|
||||
{%- assign recaptcha_enabled = true -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
{%- comment -%}
|
||||
This section is to determine whether or comment replies are enabled.
|
||||
{%- endcomment -%}
|
||||
{%- assign comment_replies_enabled = false -%}
|
||||
{%- if site.google_forms_comments.comment_replies_enabled == true -%}
|
||||
{%- assign comment_replies_enabled = true -%}
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
{%- if site.comment-read or site.google_forms_comments.google_app_script -%}
|
||||
<script>
|
||||
|
||||
{%- comment -%}
|
||||
Helper functions used so that we can forego using jQuery
|
||||
functions. When appscripts is enabled, this allows us to
|
||||
get rid of the jQuery dependency (for comments) altogether.
|
||||
{%- endcomment -%}
|
||||
function setText(elementId, textContent) {
|
||||
document.getElementById(elementId).textContent = textContent;
|
||||
};
|
||||
|
||||
function show(elementId) {
|
||||
document.getElementById(elementId).style.display = 'inline';
|
||||
};
|
||||
|
||||
function hide(elementId) {
|
||||
document.getElementById(elementId).style.display = 'none';
|
||||
};
|
||||
|
||||
function getValue(elementId) {
|
||||
return document.getElementById(elementId).value;
|
||||
};
|
||||
|
||||
function setValue(elementId, value) {
|
||||
return document.getElementById(elementId).value = value;
|
||||
};
|
||||
|
||||
function fadeIn(element) {
|
||||
var duration = 400;
|
||||
element.style.opacity = 0;
|
||||
let opacity = 0;
|
||||
const timer = setInterval(function() {
|
||||
opacity += 50 / duration;
|
||||
if (opacity >= 1) {
|
||||
clearInterval(timer);
|
||||
opacity = 1;
|
||||
}
|
||||
element.style.opacity = opacity;
|
||||
}, 50);
|
||||
};
|
||||
|
||||
|
||||
function disableButtonWithLoader(elementId) {
|
||||
var buttonToDisable = document.getElementById(elementId);
|
||||
if (buttonToDisable == null) {
|
||||
return;
|
||||
}
|
||||
buttonToDisable.disabled = true;
|
||||
setText(elementId, "\xa0");
|
||||
}
|
||||
|
||||
function disablePostCommentButton() {
|
||||
disableButtonWithLoader("comment-submit");
|
||||
};
|
||||
|
||||
function enablePostCommentButton() {
|
||||
var postCommentButton = document.getElementById("comment-submit");
|
||||
postCommentButton.disabled = false;
|
||||
setText("comment-submit", "Post");
|
||||
};
|
||||
|
||||
|
||||
{%- comment -%}
|
||||
/* Load Required 3rd-Party Scripts. We use the following:
|
||||
- jquery for DOM manipulation
|
||||
- jquery-csv for parsing Google Sheets CSV data to JSON
|
||||
- validator for escaping user-entered comments to prevent malicious code input in comments
|
||||
Make sure to update these to the latest versions every once in a while. */
|
||||
{%- endcomment -%}
|
||||
|
||||
{%- comment -%}
|
||||
/* Initial load of the comments section contnet. This ensures that we are only loading
|
||||
JQuery and other scripts once we actually need them */
|
||||
{%- endcomment -%}
|
||||
async function initialCommentLoad() {
|
||||
disableButtonWithLoader("load-comment-button");
|
||||
{%- unless site.google_forms_comments.google_app_script -%}
|
||||
{%- comment -%}
|
||||
/* Load JQuery */
|
||||
{%- endcomment -%}
|
||||
var loadJquery = document.createElement("script");
|
||||
loadJquery.type = "text/javascript";
|
||||
loadJquery.src = "https://code.jquery.com/jquery-3.6.0.min.js";
|
||||
document.head.appendChild(loadJquery);
|
||||
{%- endunless -%}
|
||||
|
||||
{%- if recaptcha_enabled -%}
|
||||
{%- comment -%}
|
||||
/* Load Recaptcha */
|
||||
{%- endcomment -%}
|
||||
var loadRecaptcha = document.createElement("script");
|
||||
loadRecaptcha.src = "https://www.google.com/recaptcha/api.js?render={{ site.google_forms_comments.recaptcha_site_key }}";
|
||||
document.head.appendChild(loadRecaptcha);
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%}
|
||||
/* Load Validator (to ensure we don't run code from comments for example) */
|
||||
{%- endcomment -%}
|
||||
var loadValidator = document.createElement("script");
|
||||
loadValidator.src = "https://cdnjs.cloudflare.com/ajax/libs/validator/13.5.2/validator.min.js";
|
||||
document.head.appendChild(loadValidator);
|
||||
|
||||
{%- comment -%}
|
||||
/* Await Scripts Loaded */
|
||||
{%- endcomment -%}
|
||||
{%- if recaptcha_enabled -%}
|
||||
await loadRecaptcha.onload;
|
||||
{%- endif -%}
|
||||
await loadValidator.onload;
|
||||
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
reloadComments();
|
||||
{%- else -%}
|
||||
loadJquery.onload = function() {
|
||||
{%- comment -%}
|
||||
/* We need to ensure that jQuery is ready before we can load JQuery CSV (used for .csv parsing) */
|
||||
{%- endcomment -%}
|
||||
async function checkJQuery() {
|
||||
if (typeof jQuery === "undefined") {
|
||||
return false;
|
||||
} else {
|
||||
clearTimeout(interval);
|
||||
|
||||
{%- comment -%}
|
||||
/* Load JQuery CSV (need regular JQuery first) */
|
||||
{%- endcomment -%}
|
||||
var loadJqueryCsv = document.createElement("script");
|
||||
loadJqueryCsv.src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-csv/1.0.11/jquery.csv.min.js";
|
||||
document.head.appendChild(loadJqueryCsv);
|
||||
|
||||
{%- comment -%}
|
||||
/* Wait for JQuery CSV to finish loading */
|
||||
{%- endcomment -%}
|
||||
await loadJqueryCsv.onload;
|
||||
|
||||
{%- comment -%}
|
||||
/* Now, load the comments */
|
||||
{%- endcomment -%}
|
||||
reloadComments();
|
||||
}
|
||||
};
|
||||
{%- comment -%}
|
||||
/* check if JQuery is ready every 250 MS
|
||||
this timer will be cleared after the check passes */
|
||||
{%- endcomment -%}
|
||||
var interval = setInterval(checkJQuery, 250);
|
||||
};
|
||||
{%- endif -%}
|
||||
};
|
||||
|
||||
var thisPageUrl = "{{ page.url }}";
|
||||
const EXTRA_INFO_START = "chunkedCommentInfoStart";
|
||||
const EXTRA_INFO_END = "chunkedCommentInfoEnd";
|
||||
|
||||
{%- comment -%}
|
||||
/* Visbile comments are those already dislayed to the user.
|
||||
Hidden comments are those that are already retrieved from Google Sheets but not shown yet (i.e. with a load more button visible).
|
||||
Net new comments are those that have been added since the comment submit button was pressed. Usually this will be the single added
|
||||
comment. */
|
||||
{%- endcomment -%}
|
||||
var visibleComments = [];
|
||||
var hiddenComments = [];
|
||||
var netNewComments = [];
|
||||
{%- if comment_replies_enabled -%}
|
||||
var commentIds = new Set();
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%}
|
||||
/* Format the comment's date to our desired format */
|
||||
{%- endcomment -%}
|
||||
function formatDate(stringDate) {
|
||||
var date = new Date(stringDate);
|
||||
var dateOptions = { year: 'numeric', month: 'long', day: 'numeric'};
|
||||
var timeOptions = { hour: 'numeric', minute: 'numeric' };
|
||||
return `${date.toLocaleDateString("en-us", dateOptions)} at ${date.toLocaleTimeString("en-us", timeOptions)}`;
|
||||
};
|
||||
|
||||
{%- comment -%}
|
||||
/* Called when the comments have been loaded or relaoded.
|
||||
Comments that we have already displayed on the page will be ignored */
|
||||
{%- endcomment -%}
|
||||
async function displayComments(comments) {
|
||||
show("comments-and-input");
|
||||
hide("load-comments");
|
||||
var hasVisibleComments = visibleComments.length > 0;
|
||||
|
||||
{%- comment -%}
|
||||
/* if we have no comments, show the 'no comments available' section */
|
||||
{%- endcomment -%}
|
||||
if (comments == null || comments === "" || comments == []) {
|
||||
if (!hasVisibleComments) {
|
||||
show("no-comments");
|
||||
hide("load-more-comments");
|
||||
hide("num-comments-displayed");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{%- if comment_replies_enabled -%}
|
||||
commentIds = new Set();
|
||||
{%- endif -%}
|
||||
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
var newHiddenComments = comments
|
||||
{%- if comment_replies_enabled -%}
|
||||
.map(item => {
|
||||
commentIds.add(Date.parse(item.timestamp)?.toString());
|
||||
return item;
|
||||
})
|
||||
{%- endif -%}
|
||||
.sort((a, b) => (new Date(a.timestamp).getTime() || 0) - (new Date(b.timestamp).getTime() || 0))
|
||||
.map(item => JSON.stringify(item));
|
||||
{%- else -%}
|
||||
{%- comment -%}
|
||||
/* Add headers so json objects are named properly */
|
||||
{%- endcomment -%}
|
||||
comments = `timestamp,name,comment,isAuthor\n${comments}`;
|
||||
var newHiddenComments = $.csv.toObjects(comments)
|
||||
{%- if comment_replies_enabled -%}
|
||||
.map(item => {
|
||||
commentIds.add(Date.parse(item.timestamp)?.toString());
|
||||
return item;
|
||||
})
|
||||
{%- endif -%}
|
||||
.sort((a, b) => (new Date(a.timestamp).getTime() || 0) - (new Date(b.timestamp).getTime() || 0))
|
||||
.map(item => JSON.stringify(item));
|
||||
{%- endif -%}
|
||||
|
||||
if (hasVisibleComments) {
|
||||
netNewComments = newHiddenComments
|
||||
.filter(item => !visibleComments.includes(item))
|
||||
.filter(item => !hiddenComments.includes(item));
|
||||
}
|
||||
|
||||
hiddenComments = newHiddenComments
|
||||
.filter(item => !visibleComments.includes(item))
|
||||
.filter(item => !netNewComments.includes(item));
|
||||
|
||||
loadMoreComments(false || !hasVisibleComments);
|
||||
};
|
||||
|
||||
{%- comment -%}
|
||||
/* Loads more comments if there are any left to display from those already loaded from the server */
|
||||
{%- endcomment -%}
|
||||
function loadMoreComments(isLoadMoreAction) {
|
||||
{%- if chunked_loading_enabled -%}
|
||||
var newCommentsToDisplay = hiddenComments.slice(-5);
|
||||
{%- else -%}
|
||||
var newCommentsToDisplay = hiddenComments;
|
||||
{%- endif -%}
|
||||
|
||||
if (isLoadMoreAction) {
|
||||
{%- comment -%}
|
||||
/* iterate over each comment that came from Google Sheets */
|
||||
{%- endcomment -%}
|
||||
newCommentsToDisplay.reverse().forEach(element => displaySingleComment(element, true));
|
||||
hiddenComments = hiddenComments.filter(item => !newCommentsToDisplay.includes(item) && !visibleComments.includes(item));
|
||||
} else {
|
||||
{%- comment -%}
|
||||
/* add the new comments if there are any */
|
||||
{%- endcomment -%}
|
||||
netNewComments.forEach(element => displaySingleComment(element, false));
|
||||
netNewComments = []
|
||||
}
|
||||
|
||||
setText("num-comments-displayed", `Showing ${visibleComments.length} of ${visibleComments.length + hiddenComments.length} comments`);
|
||||
|
||||
if (hiddenComments.length == 0) {
|
||||
hide("load-more-comments");
|
||||
} else {
|
||||
show("load-more-comments");
|
||||
}
|
||||
};
|
||||
|
||||
{%- if comment_replies_enabled -%}
|
||||
var currentFadeItem = null;
|
||||
|
||||
function fadeColor(element) {
|
||||
if (currentFadeItem != null) {
|
||||
clearInterval(currentFadeItem);
|
||||
currentFadeItem = null;
|
||||
}
|
||||
|
||||
var duration = 1000;
|
||||
var step = 25;
|
||||
let opacity = 1;
|
||||
const timer = setInterval(function() {
|
||||
opacity -= (1 / (duration / step));
|
||||
if (opacity <= 0) {
|
||||
clearInterval(timer);
|
||||
currentFadeItem = null;
|
||||
opacity = 0;
|
||||
}
|
||||
element.style.background = "rgba(3, 155, 229, "+ opacity +")";
|
||||
}, step);
|
||||
currentFadeItem = timer;
|
||||
};
|
||||
|
||||
function smoothScrollTo(element) {
|
||||
element.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
inline: "nearest"
|
||||
});
|
||||
};
|
||||
|
||||
function jumpToComment(commentId) {
|
||||
var element = document.getElementsByClassName("" + commentId)[0];
|
||||
while(element == null && hiddenComments.length > 0) {
|
||||
loadMoreComments(true);
|
||||
element = document.getElementsByClassName("" + commentId)[0];
|
||||
}
|
||||
if (element != null) {
|
||||
smoothScrollTo(element);
|
||||
fadeColor(element);
|
||||
}
|
||||
};
|
||||
|
||||
function clearCommentReplyItem() {
|
||||
document.getElementById("comment-reply-info").innerHTML = ``;
|
||||
};
|
||||
|
||||
function replyToComment(commentId) {
|
||||
document.getElementById("comment-reply-info").innerHTML = `<small class="comment-reply-clear" onclick="clearCommentReplyItem()">Ⓧ </small><small onClick="jumpToComment('${commentId}')">Replying to comment <a id="reply-to-comment-id">${commentId}</a></small>`;
|
||||
smoothScrollTo(document.getElementsByClassName("comments-form")[0]);
|
||||
};
|
||||
{%- endif -%}
|
||||
|
||||
function getCommentInfo(comment) {
|
||||
var extraCommentInfo = comment?.match(EXTRA_INFO_START + ".+" + EXTRA_INFO_END);
|
||||
return {
|
||||
text : comment?.replace(extraCommentInfo, ""),
|
||||
extraCommentInfo : JSON.parse(extraCommentInfo?.toString()?.replace(EXTRA_INFO_START, "")?.replace(EXTRA_INFO_END, "") ?? "{}")
|
||||
};
|
||||
};
|
||||
|
||||
function displaySingleComment(element, isPrepend) {
|
||||
{%- comment -%}
|
||||
/* don't re-add existing comments */
|
||||
{%- endcomment -%}
|
||||
if (visibleComments.includes(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var jsonElement = JSON.parse(element);
|
||||
var comment = getCommentInfo(jsonElement.comment);
|
||||
{%- comment -%}
|
||||
/* If the author was the one who commented back, add this 'verified' logic */
|
||||
{%- endcomment -%}
|
||||
var verifiedAuthorTag = "";
|
||||
if (jsonElement.isAuthor == true || jsonElement.isAuthor === "TRUE") {
|
||||
verifiedAuthorTag = " <strong>(Author)</strong>";
|
||||
}
|
||||
{%- comment -%}
|
||||
/* Create the new item using HTML and append it to the user-visible comment section */
|
||||
{%- endcomment -%}
|
||||
var newItem = document.createElement("div");
|
||||
newItem.classList.add("comment-item");
|
||||
{%- if comment_replies_enabled -%}
|
||||
newItem.classList.add("reply-item");
|
||||
var commentReplyId = "" + Date.parse(jsonElement.timestamp);
|
||||
newItem.classList.add(commentReplyId);
|
||||
var replyToHtml = null;
|
||||
var replyId = comment?.extraCommentInfo?.replyToCommentId;
|
||||
if (replyId != null && replyId != "" && commentIds.has(replyId)) {
|
||||
replyToHtml = `<p class="comment-reply-id" onclick="jumpToComment('${replyId}')"><<< ${replyId}</p>`;
|
||||
}
|
||||
var currentCommentReplyButton = `<span class="comment-reply-button" onclick="replyToComment(${commentReplyId})"><img />Reply</span>`;
|
||||
{%- endif -%}
|
||||
|
||||
newItem.innerHTML = `
|
||||
<p class="commenter-name">${validator.escape(jsonElement.name.toString())}${verifiedAuthorTag}
|
||||
<small>${formatDate(jsonElement.timestamp)}
|
||||
{%- if comment_replies_enabled -%}
|
||||
${currentCommentReplyButton ?? ""}
|
||||
{%- endif -%}
|
||||
</small>
|
||||
</p>
|
||||
<div>
|
||||
{%- if comment_replies_enabled -%}
|
||||
${replyToHtml ?? ""}
|
||||
{%- endif -%}
|
||||
<p>${validator.escape(comment?.text?.toString()).replace(new RegExp('\r?\n','g'), '<br />')}</p>
|
||||
</div>
|
||||
`.trim();
|
||||
if (isPrepend) {
|
||||
document.getElementById("comment-container").prepend(newItem);
|
||||
} else {
|
||||
document.getElementById("comment-container").append(newItem);
|
||||
}
|
||||
fadeIn(newItem);
|
||||
visibleComments.push(element);
|
||||
|
||||
{%- comment -%}
|
||||
/* Hide the 'no comments available' section since we have added a comment */
|
||||
{%- endcomment -%}
|
||||
hide("no-comments");
|
||||
show("num-comments-displayed");
|
||||
};
|
||||
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
function reloadComments() {
|
||||
fetch(`{{ site.google_forms_comments.google_app_script }}?url=${thisPageUrl}`)
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
var error = response?.error || null;
|
||||
if (error != null) {
|
||||
console.error(error);
|
||||
setText("comment-submit-error", error);
|
||||
show("comment-submit-error");
|
||||
} else {
|
||||
setText("comment-submit-error", '');
|
||||
hide("comment-submit-error");
|
||||
displayComments(response);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
{%- else -%}
|
||||
{%- comment -%}
|
||||
/* Calls to the Google Sheets gviz API to load the comment data
|
||||
Since we are storing comments for all of our blog articles in one sheet, we filter
|
||||
by page URL. */
|
||||
{%- endcomment -%}
|
||||
function reloadComments() {
|
||||
{%- comment -%}
|
||||
/*
|
||||
Use SQL in the gviz URL to load the correct csv.
|
||||
In this implementation, columns are mapped to the following:
|
||||
A - timestamp, the time the comment was left
|
||||
B - page url, the page the comment was left on
|
||||
C - name, the text left in the comment name field
|
||||
D - comment, the text left in the comment field
|
||||
E - isAuthor, boolean indicating if the comment was left by the actual author
|
||||
*/
|
||||
{%- endcomment -%}
|
||||
var sqlStatement = encodeURIComponent(`SELECT A, C, D, E WHERE B = '${thisPageUrl}'`);
|
||||
fetch(`{{ site.comment-read }}/gviz/tq?tqx=out:csv&sheet=comments&tq=${sqlStatement}&headers=0`)
|
||||
.then(response => response.text())
|
||||
.then(response => displayComments(response));
|
||||
};
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%}
|
||||
/* encodes the form data for http transport */
|
||||
{%- endcomment -%}
|
||||
const encodeFormData = (data) => {
|
||||
return Object.keys(data)
|
||||
.map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key]))
|
||||
.join('&');
|
||||
};
|
||||
|
||||
{%- if recaptcha_enabled -%}
|
||||
function checkRecaptcha() {
|
||||
{%- comment -%}
|
||||
Disallow posting a new comment until this one has posted or failed.
|
||||
{%- endcomment -%}
|
||||
disablePostCommentButton();
|
||||
grecaptcha.ready(function() {
|
||||
var pageAction = "comment_submit_{{ page.title }}".replace(/[^A-Za-z/_]/g, "");
|
||||
grecaptcha.execute('{{ site.google_forms_comments.recaptcha_site_key }}', { action: pageAction }).then(function(token) {
|
||||
postComment(token);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
};
|
||||
{%- endif -%}
|
||||
|
||||
|
||||
function getCommentContent() {
|
||||
var comment = getValue("comment-comment")?.toString();
|
||||
|
||||
var extraCommentInfo = {};
|
||||
|
||||
{%- if comment_replies_enabled -%}
|
||||
var replyToCommentId = document?.getElementById("reply-to-comment-id")?.text;
|
||||
if (replyToCommentId != null && replyToCommentId != "") {
|
||||
extraCommentInfo.replyToCommentId = replyToCommentId;
|
||||
}
|
||||
{%- endif -%}
|
||||
|
||||
if (Object.keys(extraCommentInfo).length !== 0) {
|
||||
comment = EXTRA_INFO_START + JSON.stringify(extraCommentInfo) + EXTRA_INFO_END + comment;
|
||||
}
|
||||
|
||||
return comment;
|
||||
};
|
||||
|
||||
{%- comment -%}
|
||||
/* Posts a comment */
|
||||
{%- endcomment -%}
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
function postComment(recaptchaToken) {
|
||||
disablePostCommentButton();
|
||||
var username = getValue("comment-name");
|
||||
var comment = getCommentContent();
|
||||
|
||||
fetch(`{{ site.google_forms_comments.google_app_script }}`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
redirect: "follow",
|
||||
headers: {
|
||||
"Content-Type": "text/plain;charset=utf-8"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url : thisPageUrl?.toString(),
|
||||
name : username?.toString(),
|
||||
comment : comment?.toString(),
|
||||
recaptchaToken: (recaptchaToken || null)
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(response => {
|
||||
var error = response?.error || null;
|
||||
if (error != null) {
|
||||
console.error(error);
|
||||
setText("comment-submit-error", error);
|
||||
show("comment-submit-error");
|
||||
} else {
|
||||
setValue("comment-name", '');
|
||||
setValue("comment-comment", '');
|
||||
setText("comment-submit-error", '');
|
||||
hide("comment-submit-error");
|
||||
displayComments(response);
|
||||
{%- if comment_replies_enabled -%}
|
||||
clearCommentReplyItem();
|
||||
{%- endif -%}
|
||||
}
|
||||
})
|
||||
.catch(error => {console.log(error);})
|
||||
.finally(() => { enablePostCommentButton(); });
|
||||
|
||||
return false;
|
||||
};
|
||||
{%- else -%}
|
||||
function postComment(recaptchaToken) {
|
||||
disablePostCommentButton();
|
||||
var username = getValue("comment-name");
|
||||
var comment = getCommentContent();
|
||||
|
||||
fetch(`{{ site.comment-post }}/formResponse`, {
|
||||
method: 'POST',
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
body: encodeFormData({
|
||||
"{{ site.comment-post-fields[0] }}" : thisPageUrl?.toString(),
|
||||
"{{ site.comment-post-fields[1] }}" : username?.toString(),
|
||||
"{{ site.comment-post-fields[2] }}" : comment?.toString()
|
||||
})
|
||||
}).then(response => {
|
||||
setValue("comment-name", '');
|
||||
setValue("comment-comment", '');
|
||||
{%- if comment_replies_enabled -%}
|
||||
clearCommentReplyItem();
|
||||
{%- endif -%}
|
||||
reloadComments();
|
||||
})
|
||||
.catch(error => {console.log(error);})
|
||||
.finally(() => { enablePostCommentButton(); });
|
||||
|
||||
return false;
|
||||
};
|
||||
{%- endif -%}
|
||||
|
||||
{%- unless lazy_load_enabled -%}
|
||||
initialCommentLoad();
|
||||
{%- endunless -%}
|
||||
</script>
|
||||
{%- comment -%}
|
||||
<!--
|
||||
HTML for displaying the actual comment section.
|
||||
It has a state for both when the article has comments and when it does not. -->
|
||||
{%- endcomment -%}
|
||||
<h4 class="post-subtitle">Comments</h4>
|
||||
<div id="load-comments" class="comments">
|
||||
<button id="load-comment-button" class="comment-button" onclick="initialCommentLoad()">Load Comments</button>
|
||||
</div>
|
||||
<div id="comments-and-input">
|
||||
<div id="comment-section" class="comments">
|
||||
<p id="num-comments-displayed"></p>
|
||||
<button id="load-more-comments" class="comment-button" onclick="loadMoreComments(true)" type="button">Load Older Comments</button>
|
||||
<div id="no-comments" class="comment-info-text">
|
||||
<p>There are currently no comments on this article, be the first to add one below</p>
|
||||
</div>
|
||||
<div id="comment-container"></div>
|
||||
</div>
|
||||
<h4 class="post-subtitle">Add a Comment</h4>
|
||||
<div class="comments">
|
||||
<div class="comment-info-text">
|
||||
<p>Note that I may remove comments for any reason, so try to be civil. If you are looking for a response to your comment, either leave your email address or check back on this page periodically.</p>
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
<p id="comment-submit-error"></p>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
<div class="comments-form">
|
||||
{%- if recaptcha_enabled -%}
|
||||
<form onsubmit="return checkRecaptcha()" id="comment-form" autocomplete="off">
|
||||
{%- else -%}
|
||||
<form onsubmit="return postComment()" id="comment-form" autocomplete="off">
|
||||
{%- endif -%}
|
||||
<ul>
|
||||
<li>
|
||||
<input id="comment-name" name="username" type="text" placeholder="Name" required="" value="">
|
||||
{%- if comment_replies_enabled -%}
|
||||
<p id="comment-reply-info"></p>
|
||||
{%- endif -%}
|
||||
</li>
|
||||
<li><textarea id="comment-comment" name="comment" placeholder="Comment" required=""></textarea></li>
|
||||
<li><button id="comment-submit" type="submit" class="comment-button">Post</button></li>
|
||||
</ul>
|
||||
{%- if recaptcha_enabled -%}
|
||||
<p id="comment-recaptcha-disclaimer">This site is protected by reCAPTCHA and the Google
|
||||
<a href="https://policies.google.com/privacy">Privacy Policy</a> and
|
||||
<a href="https://policies.google.com/terms">Terms of Service</a> apply.
|
||||
{%- endif -%}
|
||||
<p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{%- comment -%}
|
||||
<!--
|
||||
Styling information.
|
||||
This might typically be in an scss file but is added here so only a single
|
||||
file needs to be added to a project trying to implement this feature. -->
|
||||
{%- endcomment -%}
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro");
|
||||
.comment-item {
|
||||
position: relative;
|
||||
padding: 0.5em 2.5em;
|
||||
background: white;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
width: 95%;
|
||||
margin: 1em auto;
|
||||
box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.comment-item:after {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.comment-item p {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.comment-item small {
|
||||
padding-left: 0.7em;
|
||||
color: #999;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.commenter-name {
|
||||
color: #367588;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.commenter-name strong {
|
||||
color: #e91e63;
|
||||
text-decoration: none;
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
{%- if comment_replies_enabled -%}
|
||||
.comment-reply-button {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.comment-reply-button img {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
margin: 0;
|
||||
content:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23999999'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.comment-reply-button:hover {
|
||||
color: #e91e63;
|
||||
}
|
||||
|
||||
.comment-reply-button:hover img {
|
||||
content:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23e91e63'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.comment-reply-id {
|
||||
color: #e91e63;
|
||||
font-size: .9em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#comment-reply-info {
|
||||
float: right;
|
||||
font-size: .9em;
|
||||
color: #e91e63;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#comment-reply-info small {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.comment-reply-clear {
|
||||
color: #e91e63;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
{%- endif -%}
|
||||
|
||||
#comment-reply-info p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.comments-form {
|
||||
margin-left: 1em;
|
||||
margin-right: 1em;
|
||||
width: 95%;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
.comments-form ul {
|
||||
list-style: none;
|
||||
margin-left: -40px;
|
||||
}
|
||||
|
||||
.comments-form ul li {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.comments {
|
||||
align-items: center;
|
||||
border-top: 0.5px solid #e5e5e5;
|
||||
padding-top: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comment-button {
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #148ee2;
|
||||
color: #fff;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 20px;
|
||||
outline-style: none;
|
||||
padding: 10px 20px;
|
||||
width: auto;
|
||||
min-width: 25%;
|
||||
}
|
||||
|
||||
#comment-submit {
|
||||
width: auto;
|
||||
min-width: 25%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.comment-button:disabled::after,
|
||||
.comment-button[disabled]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
border: 4px solid transparent;
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
-webkit-animation: button-loading-spinner 1s ease infinite;
|
||||
animation: button-loading-spinner 1s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes button-loading-spinner {
|
||||
from {
|
||||
transform: rotate(0turn);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
|
||||
#load-more-comments {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#load-more-comments,
|
||||
#num-comments-displayed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#comment-name,
|
||||
#comment-comment {
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #f7f7f7;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name::-webkit-input-placeholder,
|
||||
#comment-comment::-webkit-input-placeholder {
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name::-moz-placeholder,
|
||||
#comment-comment::-moz-placeholder {
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name:-ms-input-placeholder,
|
||||
#comment-comment:-ms-input-placeholder {
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name::-ms-input-placeholder,
|
||||
#comment-comment::-ms-input-placeholder {
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name::placeholder,
|
||||
#comment-comment::placeholder {
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-name:focus,
|
||||
#comment-comment:focus {
|
||||
background: #fff;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
|
||||
#comment-comment {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
{%- if recaptcha_enabled -%}
|
||||
{%- comment -%}
|
||||
If you want to use the regular recaptcha badge (instead of the text currently below
|
||||
the submit button), remove this and the text disclaimer in the HTML above. Note that
|
||||
according to their FAQ page, one or the other must be displayed.
|
||||
https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-badge.-what-is-allowed
|
||||
{%- endcomment -%}
|
||||
.grecaptcha-badge {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#comment-recaptcha-disclaimer {
|
||||
text-align: justify;
|
||||
text-align-last: center;
|
||||
font-size: .7em;
|
||||
font-family: "Source Sans Pro", sans-serif;
|
||||
}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if site.google_forms_comments.google_app_script -%}
|
||||
#comment-submit-error {
|
||||
color: red;
|
||||
display: none;
|
||||
}
|
||||
{%- endif -%}
|
||||
|
||||
.comment-info-text {
|
||||
letter-spacing: 0.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comment-info-text p {
|
||||
margin: 0 0 1rem;
|
||||
text-align: justify;
|
||||
text-align-last: center;
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
#comment-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#comment-section, #load-comments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
{%- if lazy_load_enabled -%}
|
||||
#comments-and-input {
|
||||
display: none;
|
||||
}
|
||||
{%- else -%}
|
||||
#load-comments {
|
||||
display: none;
|
||||
}
|
||||
{%- endif -%}
|
||||
</style>
|
||||
{%- endif -%}
|
34
docs/_includes/footer_custom.html
Normal file
34
docs/_includes/footer_custom.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!-- Generate footer -->
|
||||
<style>
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="footer-logo">
|
||||
<a href="/">
|
||||
<img width="100px" height="30px" title="Open-Air-Logo" src="./assets/images/openair-logo.png"
|
||||
alt="logo">
|
||||
</a>
|
||||
Copyright © 2019-current OpenAir-Collective. Distributed by an <a
|
||||
href="https://github.com/openair-collective/openair-cyan/blob/main/LICENSE">GPL 3.0.</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
235
docs/_includes/form.html
Normal file
235
docs/_includes/form.html
Normal file
@ -0,0 +1,235 @@
|
||||
<head>
|
||||
<style>
|
||||
html {
|
||||
font-family: Arial, Helvetica, sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue';
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
background: #03a9f4;
|
||||
width: 100%;
|
||||
height: 11px;
|
||||
border-style: none;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-left: 3px;
|
||||
width: 99%;
|
||||
background-color: #dadce0;
|
||||
}
|
||||
|
||||
.content-form {
|
||||
padding: 5%;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-gap: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: white;
|
||||
box-shadow: 2px 2px 12px 1px rgba(8, 8, 8, 0.5);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #47464b;
|
||||
}
|
||||
|
||||
input[type=submit] {
|
||||
border: none;
|
||||
color: #FEFCFB;
|
||||
background-color: #034078;
|
||||
padding: 15px 15px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
width: 100px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
border-radius: 10px;
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
|
||||
input[type=submit]:hover {
|
||||
background-color: #1282A2;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=number],
|
||||
select {
|
||||
width: 50%;
|
||||
padding: 12px 20px;
|
||||
margin: 18px;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 1.2rem;
|
||||
color: #1282A2;
|
||||
}
|
||||
|
||||
.state {
|
||||
font-size: 1.2rem;
|
||||
color: #1282A2;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
color: #FEFCFB;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
width: 100px;
|
||||
border-radius: 10px;
|
||||
transition-duration: 0.4s;
|
||||
}
|
||||
|
||||
.button-on {
|
||||
background-color: #034078;
|
||||
}
|
||||
|
||||
.button-on:hover {
|
||||
background-color: #1282A2;
|
||||
}
|
||||
|
||||
.button-off {
|
||||
background-color: #858585;
|
||||
}
|
||||
|
||||
.button-off:hover {
|
||||
background-color: #252524;
|
||||
}
|
||||
|
||||
.ideation label {
|
||||
font-size: 1.2rem;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ideation large {
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
padding-right: 10px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ideation small {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input[type=button] {
|
||||
padding: 1px 2px;
|
||||
margin-left: 10px;
|
||||
text-align: right;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
font-size: large;
|
||||
color: #1b0c8787;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content-form">
|
||||
<div style="color: #47464b;" align="center" class="title">
|
||||
<h1>Sheet Design to Improve CYAN Capacity</h1>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<div class="card">
|
||||
<h5></h5>
|
||||
<form action="" method="POST">
|
||||
<section class="ideation">
|
||||
<p>
|
||||
<large style="font-size: xx-large;">CYAN Ideation - Sheet Design</large>
|
||||
<small style="font-size: large;">
|
||||
We are looking for a way to form calcium hydroxide (or
|
||||
magnesium hydroxide) into easily-made sheets. The idea is that this would allow us
|
||||
to extend the current single-layer CYAN design to one with multiple layers that can
|
||||
capture even more CO2. Humid CO2-filled air will be passed parallel to the
|
||||
sheets. Then the sheets will be weighed before and after to calculate the weight change,
|
||||
which is relatable to the amount of CO2 taken up. <br>
|
||||
<br>
|
||||
The calcium hydroxide will yield calcium carbonate upon absorbing CO2. Note that
|
||||
calcium carbonate is a solid that will not dissolve in water. This property could be
|
||||
used to separate out the calcium carbonate. Calcium hydroxide is slightly soluble in
|
||||
water.<br>
|
||||
<br>
|
||||
Requirements:<br>
|
||||
<ul>
|
||||
1) The sheets must be as thin and stiff as possible without being brittle and thus
|
||||
easy to break. Sheets that are no more than 0.5 mm are ideal.<br>
|
||||
<br>
|
||||
2) The sheets must not come apart when exposed to humid air.<br>
|
||||
<br>
|
||||
3) It must be easy to separate the resulting calcium carbonate from the sheet. This
|
||||
can be done by making any adhesive dissolvable in water or another easily available
|
||||
solvent, or the sheet might be entirely made from calcium hydroxide + binder.<br>
|
||||
<br>
|
||||
4) Once the calcium carbonate is removed, the sheet material must either be
|
||||
recyclable, compostable, or otherwise reusable in the process of making more
|
||||
sheets.<br>
|
||||
<br>
|
||||
5) The embodied carbon of the sheets should be as low as possible.<br> In general,
|
||||
metals, cement, and other materials that require high temperatures for manufacturing
|
||||
will be high embodied carbon sources.<br>
|
||||
</ul>
|
||||
Current Ideas:<br>
|
||||
<ul>
|
||||
1) Use a binder to make a thin film of calcium hydroxide.<br>
|
||||
<br>
|
||||
2) Adhere calcium hydroxide to a backing board of some material.<br>
|
||||
<br>
|
||||
3) Use a thin film of calcium hydroxide to form a sheet.<br>
|
||||
</ul>
|
||||
</small>
|
||||
<hr>
|
||||
|
||||
</p>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
3
docs/_includes/googleform.html
Normal file
3
docs/_includes/googleform.html
Normal file
@ -0,0 +1,3 @@
|
||||
<!-- google iframe begin - place google form embed link here-->
|
||||
|
||||
<!-- google iframe end -->
|
4
docs/_includes/image.html
Normal file
4
docs/_includes/image.html
Normal file
@ -0,0 +1,4 @@
|
||||
<figure class="image">
|
||||
<img src="{{ include.url }}" alt="{{ include.description }}">
|
||||
<figcaption>{{ include.description }}</figcaption>
|
||||
</figure>
|
14
docs/_includes/recent_articles.html
Normal file
14
docs/_includes/recent_articles.html
Normal file
@ -0,0 +1,14 @@
|
||||
<div class="recent-articles">
|
||||
<h2>Recently Published</h2>
|
||||
{% assign maxposts = 5 %}
|
||||
<ul class="post-list text-muted list-unstyled">
|
||||
{% for post in site.posts limit: maxposts %}
|
||||
<li>
|
||||
<p>
|
||||
<a class="article-link" href="">
|
||||
{{ post.title | escape }}</a>
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
1
docs/_sass/color_schemes/openair.scss
Normal file
1
docs/_sass/color_schemes/openair.scss
Normal file
@ -0,0 +1 @@
|
||||
$link-color: $blue-000;
|
17
docs/_sass/custom/custom.scss
Normal file
17
docs/_sass/custom/custom.scss
Normal file
@ -0,0 +1,17 @@
|
||||
/* * {
|
||||
color: rgb(72, 6, 252) !important;
|
||||
} */
|
||||
|
||||
/* // Print-only styles.
|
||||
@media print {
|
||||
|
||||
.side-bar,
|
||||
.page-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
max-width: auto;
|
||||
margin: 1em;
|
||||
}
|
||||
} */
|
3
docs/assets/css/just-the-docs-openaircyan.scss
Normal file
3
docs/assets/css/just-the-docs-openaircyan.scss
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
---
|
||||
{% include css/just-the-docs.scss.liquid color_scheme="openair" %}
|
3
docs/assets/js/schemes.js
Normal file
3
docs/assets/js/schemes.js
Normal file
@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
jtd.setTheme("openair");
|
32
docs/gemfile
Normal file
32
docs/gemfile
Normal file
@ -0,0 +1,32 @@
|
||||
source "https://rubygems.org"
|
||||
# Hello! This is where you manage which Jekyll version is used to run.
|
||||
# When you want to use a different version, change it below, save the
|
||||
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||
#
|
||||
# bundle exec jekyll serve
|
||||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
gem "jekyll", "~> 3.9.0"
|
||||
# This is the default theme for new Jekyll sites. You may change this to anything you like.
|
||||
gem "minima"
|
||||
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
|
||||
# uncomment the line below. To upgrade, run `bundle update github-pages`.
|
||||
# gem "github-pages", group: :jekyll_plugins
|
||||
# If you have any plugins, put them here!
|
||||
group :jekyll_plugins do
|
||||
gem "jekyll-feed", "~> 0.12"
|
||||
end
|
||||
|
||||
# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||
# and associated library.
|
||||
platforms :mingw, :x64_mingw, :mswin, :jruby do
|
||||
gem "tzinfo", "~> 2.0.4"
|
||||
gem "tzinfo-data"
|
||||
end
|
||||
|
||||
# Performance-booster for watching directories on Windows
|
||||
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
|
||||
|
||||
# GitHub page
|
||||
gem "github-pages", "~> 221", group: :jekyll_plugins
|
285
docs/gemfile.lock
Normal file
285
docs/gemfile.lock
Normal file
@ -0,0 +1,285 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (3.2.22.5)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.10)
|
||||
dnsruby (1.61.9)
|
||||
simpleidn (~> 0.1)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
ethon (0.15.0)
|
||||
ffi (>= 1.15.0)
|
||||
eventmachine (1.2.7-x64-mingw32)
|
||||
execjs (2.8.1)
|
||||
faraday (1.10.0)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.3)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
ffi (1.15.5-x64-mingw32)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (221)
|
||||
github-pages-health-check (= 1.17.9)
|
||||
jekyll (= 3.9.0)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.1.6)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.15.1)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.13.0)
|
||||
jekyll-mentions (= 1.6.0)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.16.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.3)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.7.1)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.2.0)
|
||||
jekyll-theme-cayman (= 0.2.0)
|
||||
jekyll-theme-dinky (= 0.2.0)
|
||||
jekyll-theme-hacker (= 0.2.0)
|
||||
jekyll-theme-leap-day (= 0.2.0)
|
||||
jekyll-theme-merlot (= 0.2.0)
|
||||
jekyll-theme-midnight (= 0.2.0)
|
||||
jekyll-theme-minimal (= 0.2.0)
|
||||
jekyll-theme-modernist (= 0.2.0)
|
||||
jekyll-theme-primer (= 0.6.0)
|
||||
jekyll-theme-slate (= 0.2.0)
|
||||
jekyll-theme-tactile (= 0.2.0)
|
||||
jekyll-theme-time-machine (= 0.2.0)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.12.0)
|
||||
kramdown (= 2.3.1)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.3)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.12.5, < 2.0)
|
||||
rouge (= 3.26.0)
|
||||
terminal-table (~> 1.4)
|
||||
github-pages-health-check (1.17.9)
|
||||
addressable (~> 2.3)
|
||||
dnsruby (~> 1.60)
|
||||
octokit (~> 4.0)
|
||||
public_suffix (>= 3.0, < 5.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.1)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.9.0)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (>= 1.17, < 3)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3.3)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-avatar (0.7.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.1.1)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.3.1)
|
||||
commonmarker (~> 0.14)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-commonmark-ghpages (0.1.6)
|
||||
commonmarker (~> 0.17.6)
|
||||
jekyll-commonmark (~> 1.2)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.15.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.13.0)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (~> 4.0, != 4.4.0)
|
||||
jekyll-mentions (1.6.0)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.3)
|
||||
addressable (~> 2.0)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0)
|
||||
rubyzip (>= 1.3.0, < 3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.7.1)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-cayman (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-dinky (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-hacker (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-leap-day (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-merlot (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-midnight (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-minimal (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-modernist (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.6.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-tactile (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-time-machine (0.2.0)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.12.0)
|
||||
gemoji (~> 3.0)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
kramdown (2.3.1)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.13.4-x64-mingw32)
|
||||
racc (~> 1.4)
|
||||
octokit (4.22.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (4.0.7)
|
||||
racc (1.6.0)
|
||||
rb-fsevent (0.11.1)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.5)
|
||||
rouge (3.26.0)
|
||||
ruby-enum (0.9.0)
|
||||
i18n
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sawyer (0.8.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (> 0.8, < 2.0)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (2.0.4)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.1-x64-mingw32)
|
||||
unicode-display_width (1.8.0)
|
||||
wdm (0.1.1)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
github-pages (~> 221)
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-feed (~> 0.12)
|
||||
minima
|
||||
tzinfo (~> 2.0.4)
|
||||
tzinfo-data
|
||||
wdm (~> 0.1.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.29
|
8
docs/help-child.md
Normal file
8
docs/help-child.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: default
|
||||
title: Comments and Suggestions
|
||||
nav_order: 4
|
||||
has_children: false
|
||||
---
|
||||
|
||||
{% include comments.html url=page.url %}
|
8
docs/help.md
Normal file
8
docs/help.md
Normal file
@ -0,0 +1,8 @@
|
||||
---
|
||||
layout: default
|
||||
title: HELP! Ideas Needed
|
||||
nav_order: 4
|
||||
has_children: true
|
||||
---
|
||||
|
||||
{% include googleform.html %}
|
@ -1,143 +1,143 @@
|
||||
import kivy
|
||||
from multiprocessing import Process,Queue,Pipe
|
||||
kivy.require("1.9.1")
|
||||
from kivy.app import App
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.slider import Slider
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.scatter import Scatter
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.core.window import Window
|
||||
###############################################################################
|
||||
|
||||
Window.size = (700, 200)
|
||||
|
||||
class WidgetContainer(GridLayout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
super(WidgetContainer, self).__init__(**kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
############################################################################### right
|
||||
|
||||
self.cols = 3
|
||||
self.xcc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size X R'))
|
||||
self.add_widget(self.xcc)
|
||||
self.xValue = Label(text ='1')
|
||||
self.add_widget(self.xValue)
|
||||
self.xcc.bind(value = self.on_value)
|
||||
|
||||
############################################################################### bottom
|
||||
|
||||
self.Y = Slider(min = 1, max = 2000,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size Y R'))
|
||||
self.add_widget(self.Y)
|
||||
self.YV = Label(text ='1')
|
||||
self.add_widget(self.YV)
|
||||
self.Y.bind(value = self.on_value1)
|
||||
|
||||
############################################################################### left
|
||||
|
||||
self.xlc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size X L'))
|
||||
self.add_widget(self.xlc)
|
||||
self.xlValue = Label(text ='1')
|
||||
self.add_widget(self.xlValue)
|
||||
self.xlc.bind(value = self.on_value2)
|
||||
|
||||
############################################################################### top
|
||||
|
||||
self.ylc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size Y L'))
|
||||
self.add_widget(self.ylc)
|
||||
self.ylValue = Label(text ='1')
|
||||
self.add_widget(self.ylValue)
|
||||
self.ylc.bind(value = self.on_value3)
|
||||
|
||||
############################################################################### detection
|
||||
|
||||
self.deth = Slider(min = 1, max = 40,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Detection thresh DEFAULT:18'))
|
||||
self.add_widget(self.deth)
|
||||
self.dethv= Label(text ='1')
|
||||
self.add_widget(self.dethv)
|
||||
self.deth.bind(value = self.on_value4)
|
||||
|
||||
############################################################################### camera input
|
||||
|
||||
self.cam = Slider(min = 0, max = 5,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Camera input'))
|
||||
self.add_widget(self.cam)
|
||||
self.camv= Label(text ='Select')
|
||||
self.add_widget(self.camv)
|
||||
self.cam.bind(value = self.on_value5)
|
||||
|
||||
###############################################################################
|
||||
|
||||
def on_value(self, instance, brightness):
|
||||
self.xValue.text = "% d"% brightness
|
||||
fx= open("valueX.txt","w+")
|
||||
fx.write(self.xValue.text)
|
||||
fx.close
|
||||
|
||||
def on_value1(self, instance, brightness,):
|
||||
self.YV.text = "% d"% brightness
|
||||
fy= open("valueY.txt","w+")
|
||||
fy.write(self.YV.text)
|
||||
fy.close
|
||||
|
||||
def on_value2(self, instance, brightness):
|
||||
self.xlValue.text = "% d"% brightness
|
||||
fxl= open("valueXl.txt","w+")
|
||||
fxl.write(self.xlValue.text)
|
||||
fxl.close
|
||||
|
||||
def on_value3(self, instance, brightness,):
|
||||
self.ylValue.text = "% d"% brightness
|
||||
fyl= open("valueYl.txt","w+")
|
||||
fyl.write(self.ylValue.text)
|
||||
fyl.close
|
||||
|
||||
def on_value4(self, instance, brightness,):
|
||||
self.dethv.text = "% d"% brightness
|
||||
fyl= open("thresh.txt","w+")
|
||||
fyl.write(self.dethv.text)
|
||||
fyl.close
|
||||
|
||||
def on_value5(self, instance, brightness,):
|
||||
self.camv.text = "% d"% brightness
|
||||
fyl= open("camport.txt","w+")
|
||||
fyl.write(self.camv.text)
|
||||
fyl.close
|
||||
|
||||
class EyetrackGUI(App):
|
||||
def build(self):
|
||||
widgetContainer = WidgetContainer()
|
||||
print()
|
||||
|
||||
return widgetContainer
|
||||
|
||||
|
||||
rootGUI = EyetrackGUI()
|
||||
|
||||
|
||||
rootGUI.run()
|
||||
import kivy
|
||||
from multiprocessing import Process,Queue,Pipe
|
||||
kivy.require("1.9.1")
|
||||
from kivy.app import App
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.slider import Slider
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.floatlayout import FloatLayout
|
||||
from kivy.properties import NumericProperty
|
||||
from kivy.uix.scatter import Scatter
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.core.window import Window
|
||||
###############################################################################
|
||||
|
||||
Window.size = (700, 200)
|
||||
|
||||
class WidgetContainer(GridLayout):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
super(WidgetContainer, self).__init__(**kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
############################################################################### right
|
||||
|
||||
self.cols = 3
|
||||
self.xcc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size X R'))
|
||||
self.add_widget(self.xcc)
|
||||
self.xValue = Label(text ='1')
|
||||
self.add_widget(self.xValue)
|
||||
self.xcc.bind(value = self.on_value)
|
||||
|
||||
############################################################################### bottom
|
||||
|
||||
self.Y = Slider(min = 1, max = 2000,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size Y R'))
|
||||
self.add_widget(self.Y)
|
||||
self.YV = Label(text ='1')
|
||||
self.add_widget(self.YV)
|
||||
self.Y.bind(value = self.on_value1)
|
||||
|
||||
############################################################################### left
|
||||
|
||||
self.xlc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size X L'))
|
||||
self.add_widget(self.xlc)
|
||||
self.xlValue = Label(text ='1')
|
||||
self.add_widget(self.xlValue)
|
||||
self.xlc.bind(value = self.on_value2)
|
||||
|
||||
############################################################################### top
|
||||
|
||||
self.ylc = Slider(min = 1, max = 800,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Search Size Y L'))
|
||||
self.add_widget(self.ylc)
|
||||
self.ylValue = Label(text ='1')
|
||||
self.add_widget(self.ylValue)
|
||||
self.ylc.bind(value = self.on_value3)
|
||||
|
||||
############################################################################### detection
|
||||
|
||||
self.deth = Slider(min = 1, max = 40,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Detection thresh DEFAULT:18'))
|
||||
self.add_widget(self.deth)
|
||||
self.dethv= Label(text ='1')
|
||||
self.add_widget(self.dethv)
|
||||
self.deth.bind(value = self.on_value4)
|
||||
|
||||
############################################################################### camera input
|
||||
|
||||
self.cam = Slider(min = 0, max = 5,
|
||||
value_track = True,
|
||||
value_track_color =[1, 1, 1, 1])
|
||||
self.add_widget(Label(text ='Camera input'))
|
||||
self.add_widget(self.cam)
|
||||
self.camv= Label(text ='Select')
|
||||
self.add_widget(self.camv)
|
||||
self.cam.bind(value = self.on_value5)
|
||||
|
||||
###############################################################################
|
||||
|
||||
def on_value(self, instance, brightness):
|
||||
self.xValue.text = "% d"% brightness
|
||||
fx= open("valueX.txt","w+")
|
||||
fx.write(self.xValue.text)
|
||||
fx.close
|
||||
|
||||
def on_value1(self, instance, brightness,):
|
||||
self.YV.text = "% d"% brightness
|
||||
fy= open("valueY.txt","w+")
|
||||
fy.write(self.YV.text)
|
||||
fy.close
|
||||
|
||||
def on_value2(self, instance, brightness):
|
||||
self.xlValue.text = "% d"% brightness
|
||||
fxl= open("valueXl.txt","w+")
|
||||
fxl.write(self.xlValue.text)
|
||||
fxl.close
|
||||
|
||||
def on_value3(self, instance, brightness,):
|
||||
self.ylValue.text = "% d"% brightness
|
||||
fyl= open("valueYl.txt","w+")
|
||||
fyl.write(self.ylValue.text)
|
||||
fyl.close
|
||||
|
||||
def on_value4(self, instance, brightness,):
|
||||
self.dethv.text = "% d"% brightness
|
||||
fyl= open("thresh.txt","w+")
|
||||
fyl.write(self.dethv.text)
|
||||
fyl.close
|
||||
|
||||
def on_value5(self, instance, brightness,):
|
||||
self.camv.text = "% d"% brightness
|
||||
fyl= open("camport.txt","w+")
|
||||
fyl.write(self.camv.text)
|
||||
fyl.close
|
||||
|
||||
class EyetrackGUI(App):
|
||||
def build(self):
|
||||
widgetContainer = WidgetContainer()
|
||||
print()
|
||||
|
||||
return widgetContainer
|
||||
|
||||
|
||||
rootGUI = EyetrackGUI()
|
||||
|
||||
|
||||
rootGUI.run()
|
@ -1,40 +1,40 @@
|
||||
from threading import Thread
|
||||
import cv2
|
||||
import os, time
|
||||
|
||||
class config:
|
||||
X_RES = 128
|
||||
Y_RES = 128
|
||||
|
||||
class ThreadedCamera(object):
|
||||
|
||||
def __init__(self, src=0):
|
||||
self.capture = cv2.VideoCapture(src)
|
||||
self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 3)
|
||||
# Start frame retrieval thread
|
||||
self.thread = Thread(target=self.update, args=())
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def update(self):
|
||||
while True:
|
||||
if self.capture.isOpened():
|
||||
(self.ret, self.frame) = self.capture.read()
|
||||
|
||||
def show_frame(self):
|
||||
img = cv2.cvtColor(self.frame, cv2.COLOR_RGB2GRAY)
|
||||
img = cv2.resize(img, dsize=(config.X_RES, config.Y_RES))
|
||||
|
||||
cv2.imshow('frame', img)
|
||||
cv2.waitKey(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
#src = 'http://192.168.0.202:81/stream'
|
||||
#Format is http://{ip address}:81/stream
|
||||
src = input('enter ip of cam. (Format is http://{ip address}:81/stream) ')
|
||||
threaded_camera = ThreadedCamera(src)
|
||||
while True:
|
||||
try:
|
||||
threaded_camera.show_frame()
|
||||
except AttributeError:
|
||||
pass
|
||||
from threading import Thread
|
||||
import cv2
|
||||
import os, time
|
||||
|
||||
class config:
|
||||
X_RES = 128
|
||||
Y_RES = 128
|
||||
|
||||
class ThreadedCamera(object):
|
||||
|
||||
def __init__(self, src=0):
|
||||
self.capture = cv2.VideoCapture(src)
|
||||
self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 3)
|
||||
# Start frame retrieval thread
|
||||
self.thread = Thread(target=self.update, args=())
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def update(self):
|
||||
while True:
|
||||
if self.capture.isOpened():
|
||||
(self.ret, self.frame) = self.capture.read()
|
||||
|
||||
def show_frame(self):
|
||||
img = cv2.cvtColor(self.frame, cv2.COLOR_RGB2GRAY)
|
||||
img = cv2.resize(img, dsize=(config.X_RES, config.Y_RES))
|
||||
|
||||
cv2.imshow('frame', img)
|
||||
cv2.waitKey(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
#src = 'http://192.168.0.202:81/stream'
|
||||
#Format is http://{ip address}:81/stream
|
||||
src = input('enter ip of cam. (Format is http://{ip address}:81/stream) ')
|
||||
threaded_camera = ThreadedCamera(src)
|
||||
while True:
|
||||
try:
|
||||
threaded_camera.show_frame()
|
||||
except AttributeError:
|
||||
pass
|
@ -1,172 +1,172 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
|
||||
try:
|
||||
fy= open("camport.txt","r+")
|
||||
camport = fy.read().strip()
|
||||
fy.close
|
||||
except:
|
||||
camport = 0
|
||||
cam = open('camport.txt', 'w+')
|
||||
cam.write('0')
|
||||
cam.close
|
||||
print('Error: Run Gui first and adjust camera port')
|
||||
time.sleep(1)
|
||||
bl = False
|
||||
cap = cv2.VideoCapture(int(camport))
|
||||
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
print(width, height)
|
||||
|
||||
|
||||
def lefteye():
|
||||
x = 1 #defining variables for smoothing
|
||||
y = 1
|
||||
x1 = 1
|
||||
y1 = 1
|
||||
h = 1 #defines hight value so blink check doesnt error out
|
||||
|
||||
def smooth(x, x1, y, y1):
|
||||
xsmooth = (x1 + x) / 2
|
||||
ysmooth = (y1 + y) / 2
|
||||
print('x filtered:', xsmooth, 'y filtered:', ysmooth, 'x raw:', x, 'y raw:', y)
|
||||
#print('x raw:', x, 'y raw:', y)
|
||||
|
||||
|
||||
while True: #loop for eye detection
|
||||
ret, frame = cap.read()
|
||||
if ret is False:
|
||||
break
|
||||
|
||||
################################################ Reads values set in gui, needs rework to single config file
|
||||
try:
|
||||
fy= open("valueY.txt","r+")
|
||||
vy = fy.read().strip()
|
||||
fy.close
|
||||
except:
|
||||
vy = str(height)
|
||||
v= open("valueY.txt","w+")
|
||||
v.write(vy)
|
||||
v.close()
|
||||
print('WARNING: Run Gui first and adjust Value Y')
|
||||
#time.sleep(2)
|
||||
################################################
|
||||
try:
|
||||
fx= open("valueX.txt","r+")
|
||||
vx = fx.read().strip()
|
||||
fx.close
|
||||
except:
|
||||
vx = str(width)
|
||||
fx= open("valueX.txt","w+")
|
||||
fx.write(vx)
|
||||
fx.close()
|
||||
print('WARNING: Run Gui first and adjust Value X')
|
||||
#time.sleep(2)
|
||||
################################################
|
||||
try:
|
||||
vyll= open("valueYl.txt","r+")
|
||||
vyl = vyll.read().strip()
|
||||
vyll.close
|
||||
except:
|
||||
vyll= open("valueYl.txt","w+")
|
||||
vyll.write('1')
|
||||
vyll.close
|
||||
vyl = 1
|
||||
print('WARNING: Run Gui first and adjust Value Y L')
|
||||
time.sleep(1)
|
||||
################################################
|
||||
try:
|
||||
vxll= open("valueXl.txt","r+")
|
||||
vxl = vxll.read().strip()
|
||||
vxll.close
|
||||
except:
|
||||
vxl = 1
|
||||
vyll= open("valueXl.txt","w+")
|
||||
vyl = vyll.write('1')
|
||||
vyll.close
|
||||
print('Error: Run Gui first and adjust Value X L')
|
||||
time.sleep(1)
|
||||
################################################
|
||||
try:
|
||||
thresh= open("thresh.txt","r+")
|
||||
threshr = thresh.read().strip()
|
||||
thresh.close
|
||||
except:
|
||||
thresh= open("thresh.txt","w+")
|
||||
threshr = thresh.write('19')
|
||||
thresh.close
|
||||
print('WARNING: Run Gui first and adjust threshold value')
|
||||
threshr = 19
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
################################################
|
||||
# try:
|
||||
try: # trys at set size if it errors it will revert to working size/ doesnt do what was orrigionally planed, it kinda helps
|
||||
roi = frame[int(vxl): int(float(vy)), int(vyl): int(float(vx))]
|
||||
except:
|
||||
roi = frame[100: 300, 200: 316]
|
||||
try:
|
||||
x1 = x
|
||||
y1 = y
|
||||
rows, cols, _ = roi.shape
|
||||
gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
||||
gray_roi = cv2.GaussianBlur(gray_roi, (7, 7), 0)
|
||||
|
||||
_, threshold = cv2.threshold(gray_roi, int(threshr), 255, cv2.THRESH_BINARY_INV)
|
||||
contours, _ = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
contours = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)
|
||||
|
||||
for cnt in contours:
|
||||
|
||||
(x, y, w, h) = cv2.boundingRect(cnt)
|
||||
|
||||
|
||||
openes = 95
|
||||
if h <= 25: #way to find if eye is closed and sets value (hopefully will train tensorflow model for correct openess detection)
|
||||
openes = 0
|
||||
|
||||
smooth(x, x1, y, y1)
|
||||
#print('Left: x:', x, 'y:', y, 'openess:', openes)
|
||||
#print('Right: x:', x, 'y:', y, 'openess:', openes)
|
||||
|
||||
cv2.line(threshold, (x + int(w/2), 0), (x + int(w/2), rows), (255, 0, 0), 1) #visualizes eyetracking on threshold
|
||||
cv2.line(threshold, (0, y + int(h/2)), (cols, y + int(h/2)), (255, 0, 0), 1)
|
||||
cv2.drawContours(threshold, [cnt], -1, (255, 0, 0), 3)
|
||||
cv2.rectangle(threshold, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
|
||||
cv2.line(gray_roi, (x + int(w/2), 0), (x + int(w/2), rows), (255, 0, 0), 1) #visualizes eyetracking on greyscale
|
||||
cv2.line(gray_roi, (0, y + int(h/2)), (cols, y + int(h/2)), (255, 0, 0), 1)
|
||||
cv2.drawContours(gray_roi, [cnt], -1, (255, 0, 0), 3)
|
||||
cv2.rectangle(gray_roi, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
break
|
||||
|
||||
if h == 0: #basically useless lol
|
||||
print('no eye detected"')
|
||||
|
||||
|
||||
|
||||
cv2.imshow("Threshold", threshold)
|
||||
cv2.imshow("GreyScale", gray_roi)
|
||||
#cv2.imshow("Roi", roi)
|
||||
key = cv2.waitKey(30)
|
||||
except:
|
||||
print('ERROR 1: Something went wrong trying to track your eye.')
|
||||
|
||||
|
||||
|
||||
print('###############> initailizing <###############')
|
||||
|
||||
cv2.destroyAllWindows()
|
||||
if __name__ == '__main__':
|
||||
lefteye()
|
||||
#t1 = threading.Thread(target=lefteye)
|
||||
#t2 = threading.Thread(target=righteye)
|
||||
#t1.start()
|
||||
#t2.start()
|
||||
#t1.join()
|
||||
#t2.join()
|
||||
print('Running:')
|
||||
import cv2
|
||||
import numpy as np
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
|
||||
try:
|
||||
fy= open("camport.txt","r+")
|
||||
camport = fy.read().strip()
|
||||
fy.close
|
||||
except:
|
||||
camport = 0
|
||||
cam = open('camport.txt', 'w+')
|
||||
cam.write('0')
|
||||
cam.close
|
||||
print('Error: Run Gui first and adjust camera port')
|
||||
time.sleep(1)
|
||||
bl = False
|
||||
cap = cv2.VideoCapture(int(camport))
|
||||
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
|
||||
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
||||
print(width, height)
|
||||
|
||||
|
||||
def lefteye():
|
||||
x = 1 #defining variables for smoothing
|
||||
y = 1
|
||||
x1 = 1
|
||||
y1 = 1
|
||||
h = 1 #defines hight value so blink check doesnt error out
|
||||
|
||||
def smooth(x, x1, y, y1):
|
||||
xsmooth = (x1 + x) / 2
|
||||
ysmooth = (y1 + y) / 2
|
||||
print('x filtered:', xsmooth, 'y filtered:', ysmooth, 'x raw:', x, 'y raw:', y)
|
||||
#print('x raw:', x, 'y raw:', y)
|
||||
|
||||
|
||||
while True: #loop for eye detection
|
||||
ret, frame = cap.read()
|
||||
if ret is False:
|
||||
break
|
||||
|
||||
################################################ Reads values set in gui, needs rework to single config file
|
||||
try:
|
||||
fy= open("valueY.txt","r+")
|
||||
vy = fy.read().strip()
|
||||
fy.close
|
||||
except:
|
||||
vy = str(height)
|
||||
v= open("valueY.txt","w+")
|
||||
v.write(vy)
|
||||
v.close()
|
||||
print('WARNING: Run Gui first and adjust Value Y')
|
||||
#time.sleep(2)
|
||||
################################################
|
||||
try:
|
||||
fx= open("valueX.txt","r+")
|
||||
vx = fx.read().strip()
|
||||
fx.close
|
||||
except:
|
||||
vx = str(width)
|
||||
fx= open("valueX.txt","w+")
|
||||
fx.write(vx)
|
||||
fx.close()
|
||||
print('WARNING: Run Gui first and adjust Value X')
|
||||
#time.sleep(2)
|
||||
################################################
|
||||
try:
|
||||
vyll= open("valueYl.txt","r+")
|
||||
vyl = vyll.read().strip()
|
||||
vyll.close
|
||||
except:
|
||||
vyll= open("valueYl.txt","w+")
|
||||
vyll.write('1')
|
||||
vyll.close
|
||||
vyl = 1
|
||||
print('WARNING: Run Gui first and adjust Value Y L')
|
||||
time.sleep(1)
|
||||
################################################
|
||||
try:
|
||||
vxll= open("valueXl.txt","r+")
|
||||
vxl = vxll.read().strip()
|
||||
vxll.close
|
||||
except:
|
||||
vxl = 1
|
||||
vyll= open("valueXl.txt","w+")
|
||||
vyl = vyll.write('1')
|
||||
vyll.close
|
||||
print('Error: Run Gui first and adjust Value X L')
|
||||
time.sleep(1)
|
||||
################################################
|
||||
try:
|
||||
thresh= open("thresh.txt","r+")
|
||||
threshr = thresh.read().strip()
|
||||
thresh.close
|
||||
except:
|
||||
thresh= open("thresh.txt","w+")
|
||||
threshr = thresh.write('19')
|
||||
thresh.close
|
||||
print('WARNING: Run Gui first and adjust threshold value')
|
||||
threshr = 19
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
################################################
|
||||
# try:
|
||||
try: # trys at set size if it errors it will revert to working size/ doesnt do what was orrigionally planed, it kinda helps
|
||||
roi = frame[int(vxl): int(float(vy)), int(vyl): int(float(vx))]
|
||||
except:
|
||||
roi = frame[100: 300, 200: 316]
|
||||
try:
|
||||
x1 = x
|
||||
y1 = y
|
||||
rows, cols, _ = roi.shape
|
||||
gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
||||
gray_roi = cv2.GaussianBlur(gray_roi, (7, 7), 0)
|
||||
|
||||
_, threshold = cv2.threshold(gray_roi, int(threshr), 255, cv2.THRESH_BINARY_INV)
|
||||
contours, _ = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
contours = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)
|
||||
|
||||
for cnt in contours:
|
||||
|
||||
(x, y, w, h) = cv2.boundingRect(cnt)
|
||||
|
||||
|
||||
openes = 95
|
||||
if h <= 25: #way to find if eye is closed and sets value (hopefully will train tensorflow model for correct openess detection)
|
||||
openes = 0
|
||||
|
||||
smooth(x, x1, y, y1)
|
||||
#print('Left: x:', x, 'y:', y, 'openess:', openes)
|
||||
#print('Right: x:', x, 'y:', y, 'openess:', openes)
|
||||
|
||||
cv2.line(threshold, (x + int(w/2), 0), (x + int(w/2), rows), (255, 0, 0), 1) #visualizes eyetracking on threshold
|
||||
cv2.line(threshold, (0, y + int(h/2)), (cols, y + int(h/2)), (255, 0, 0), 1)
|
||||
cv2.drawContours(threshold, [cnt], -1, (255, 0, 0), 3)
|
||||
cv2.rectangle(threshold, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
|
||||
cv2.line(gray_roi, (x + int(w/2), 0), (x + int(w/2), rows), (255, 0, 0), 1) #visualizes eyetracking on greyscale
|
||||
cv2.line(gray_roi, (0, y + int(h/2)), (cols, y + int(h/2)), (255, 0, 0), 1)
|
||||
cv2.drawContours(gray_roi, [cnt], -1, (255, 0, 0), 3)
|
||||
cv2.rectangle(gray_roi, (x, y), (x + w, y + h), (255, 0, 0), 2)
|
||||
break
|
||||
|
||||
if h == 0: #basically useless lol
|
||||
print('no eye detected"')
|
||||
|
||||
|
||||
|
||||
cv2.imshow("Threshold", threshold)
|
||||
cv2.imshow("GreyScale", gray_roi)
|
||||
#cv2.imshow("Roi", roi)
|
||||
key = cv2.waitKey(30)
|
||||
except:
|
||||
print('ERROR 1: Something went wrong trying to track your eye.')
|
||||
|
||||
|
||||
|
||||
print('###############> initailizing <###############')
|
||||
|
||||
cv2.destroyAllWindows()
|
||||
if __name__ == '__main__':
|
||||
lefteye()
|
||||
#t1 = threading.Thread(target=lefteye)
|
||||
#t2 = threading.Thread(target=righteye)
|
||||
#t1.start()
|
||||
#t2.start()
|
||||
#t1.join()
|
||||
#t2.join()
|
||||
print('Running:')
|
Loading…
Reference in New Issue
Block a user