Parcourir la source

Update of quest expe model

Jérôme BUISINE il y a 4 ans
Parent
commit
3eefb440ce

+ 1 - 0
.gitignore

@@ -116,4 +116,5 @@ GitHub.sublime-settings
 .history
 
 expe_results
+expe_models
 media

+ 1 - 0
expe/config.py

@@ -5,6 +5,7 @@ GET_SCENE_IMAGE_API_URL     = DIRAN_DOMAIN_NAME + "api/getImage?sceneName={0}&im
 GET_SCENES_API_URL          = DIRAN_DOMAIN_NAME + "api/listScenes"
 
 # folder variables
+model_expe_folder           = "expes_models/{0}/"
 output_expe_folder          = "expes_results/{0}/"
 output_tmp_folder           = "tmp"
 

+ 1 - 103
expe/quest/expe.py

@@ -36,106 +36,4 @@ def logistic(x, params, corr_at_thresh=threshold_prob, chance_level=chance_level
 
 # that's a wrapper function to specify wich  psychometric function one we want to use for the QUEST procedure
 def psychometric_fun( x , params ):
-    return logistic(x , params ,  corr_at_thresh=threshold_prob, chance_level=chance_level )
-
-
-
-#create results directory if not exist
-if not os.path.exists(cfg.output_expe_folder):
-    os.makedirs(cfg.output_expe_folder)
-  
-timestamp = datetime.strftime(datetime.utcnow(), "%Y-%m-%d_%Hh%Mm%Ss")
-filename += "online_ans" + timestamp +".csv"
-f = open(filename,"w")
-
-#orientation : 0 = vertical, 1 = horizontal
-#image_ref_position : 0 = right/bottom, 1 = left/up
-#answer : left = 1, right = 0
-f.write('stimulus' + ";" + "name_stimulus" + ";" + 'cropping_percentage' + ";" + 'orientation' + ';' 
-         + 'image_ref_position' + ';' + 'answer' + ';' + 'time_reaction' + ';' + 'entropy' + '\n')
-#k=np.linspace(50,20000,50)
-#k=k.astype(int)
-dd_sam=[]
-
-for i in range(len(files)):
-    ff = [int(s) for s in re.findall(r'\d+', files[i])]
-    dd_sam.append(ff[0])
-
-
-dd_sam=np.array(dd_sam)
-    
-    
-thresholds = np.arange(50, 10000, 50)
-stim_space=np.asarray(dd_sam)
-slopes = np.arange(0.0001, 0.001, 0.00003)
-
-#mywin = visual.Window([800,600], monitor="testMonitor", screen = 1, units="deg",fullscr=True)
-qp = QuestPlus(stim_space, [thresholds, slopes], function=psychometric_fun)
-answerTime=[]
-r=[]
-
-dataset = "contemporary"
-
-#image_ref = Image.open(data_folder + files[-1])
-image_ref = get_image(diran_domain_name, dataset, 10000)
-for i in range(5):
-    next_stim = qp.next_contrast()
-    print(next_stim)
-    #next_idx = np.where(dd_sam==next_stim)
-    #print(files[next_idx[0][0]])
-    #image_path = data_folder + files[next_idx[0][0]]
-    #current_image = Image.open(image_path)
-    current_image= get_image(diran_domain_name, dataset, next_stim)
-    crop_image, percentage, orientation, position = crop_images(image_ref, current_image)
-
-    answerTime.append(end-start)
-    if key_answer == ['left']:
-        answer = 1 #one image
-    else: 
-        answer = 0  #two images
-    r.append(answer)
-    qp.update(dd_sam[i], answer) 
-    
-    entropy = qp.get_entropy()
-
-    print(entropy)
-    
-    f.write(str(next_stim) + ";" + dataset + ";" + str(percentage) + ";" + str(orientation) + ";" 
-            + str(position) + ";" + str(answer) + ";" + str(answerTime[-1]) + ";" + str(entropy) +'\n')
-    f.flush()
-
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
+    return logistic(x , params ,  corr_at_thresh=threshold_prob, chance_level=chance_level)

+ 3 - 1
expe/templates/expe/expe.html

@@ -22,7 +22,9 @@
     {% endif %}
 
     {% block javascripts %}
-        <script src="{% static "js/pnglib.js" %}"></script>
+        <script type="text/javascript"> 
+            var BEGIN_EXPE = "{{request.session.begin}}"
+        </script>
         <script src="{% static "js/loadImg.js" %}"></script>
         <script src="{% static "js/keyEvents.js" %}"></script>
     {% endblock %}

+ 2 - 0
expe/templates/expe/expe_list.html

@@ -31,6 +31,8 @@
                     </select>
                 </div>
 
+                <!--<input type="hidden" value="0" name="iteration"/>-->
+
                 <button type="submit" class="btn btn-primary">Submit</button>
             </form>
         </div>

+ 145 - 41
expe/views.py

@@ -10,6 +10,12 @@ import base64
 import random
 import numpy as np
 from datetime import datetime
+import pickle 
+import time
+
+# expe imports
+from .quest.quest_plus import QuestPlus
+from .quest.expe import psychometric_fun
 
 # image processing imports
 import io
@@ -46,47 +52,30 @@ def expe(request):
         request.session['id'] = functions.uniqueID()
 
     # first time expe is launched add expe information
-    if 'expe' not in request.session:
+    if 'expe' not in request.session or expe_name != request.session.get('expe'):
         request.session['expe'] = expe_name
+        request.session['scene'] = scene_name
+
         request.session['begin'] = True
+        request.session['qualities'] = api.get_scene_qualities(scene_name)
+        # update unique timestamp each time new experience is launched
+        request.session['timestamp'] = datetime.strftime(datetime.utcnow(), "%Y-%m-%d_%Hh%Mm%Ss")
+
     else:
         request.session['begin'] = False
 
-    print(request.session.get('begin'))
+    # refresh if scene_name changed
+    if 'scene' not in request.session or scene_name != request.session.get('scene'):
+        request.session['expe'] = expe_name
+        request.session['scene'] = scene_name
 
-    # update ref img at first time or expe changed
-    if expe_name != request.session.get('expe'):
-    #if 'ref_img' not in request.session or expe_name != request.session.get('expe'):
         request.session['begin'] = True
         request.session['qualities'] = api.get_scene_qualities(scene_name)
         # update unique timestamp each time new experience is launched
         request.session['timestamp'] = datetime.strftime(datetime.utcnow(), "%Y-%m-%d_%Hh%Mm%Ss")
 
-        # TODO : add in cache ref_image
-        # get reference image
-        #ref_image = api.get_image(scene_name, 'max')
-        # save ref image as list (can't save python object)
-        #request.session['ref_img'] = np.array(ref_image).tolist()
-
-    # construct new image
-    quality = random.choice(request.session.get('qualities'))
-    noisy_image = api.get_image(scene_name, quality)
-
-    # reconstruct reference image from list stored into session
-    # ref_image = Image.fromarray(np.array(request.session.get('ref_img')))
-    ref_image = api.get_image(scene_name, 'max')
-    img_merge, per, orien, swap_img = crop_images(noisy_image, ref_image)
-
-    # create output folder for tmp files if necessary
-    tmp_folder = os.path.join(settings.MEDIA_ROOT, cfg.output_tmp_folder)
-
-    if not os.path.exists(tmp_folder):
-        os.makedirs(tmp_folder)
-
-    # generate tmp merged image (pass as BytesIO was complicated..)
-    # TODO : add crontab task to erase generated img
-    filepath_img = os.path.join(tmp_folder, request.session.get('id') + '_' + scene_name + '' + expe_name + '.png')
-    img_merge.save(filepath_img)
+    else:
+        request.session['begin'] = False
 
     # create output folder for expe_result
     current_day = datetime.strftime(datetime.utcnow(), "%Y-%m-%d")
@@ -95,21 +84,39 @@ def expe(request):
     if not os.path.exists(results_folder):
         os.makedirs(results_folder)
 
-    result_filename = expe_name + '_' + request.session.get('id') + '_' + request.session.get('timestamp') +".csv"
+    result_filename = expe_name + '_' + scene_name + '_' + request.session.get('id') + '_' + request.session.get('timestamp') +".csv"
     results_filepath = os.path.join(results_folder, result_filename)
 
     if not os.path.exists(results_filepath):
-        f = open(results_filepath, 'w')
-        functions.write_header_expe(f, expe_name)
+        output_file = open(results_filepath, 'w')
+        functions.write_header_expe(output_file, expe_name)
     else:
-        f = open(results_filepath, 'a')
+        output_file = open(results_filepath, 'a')
 
-    #orientation : 0 = vertical, 1 = horizontal
-    #image_ref_position : 0 = right/bottom, 1 = left/up
-    #answer : left = 1, right = 0
+    # create `quest` object if not exists    
+    models_folder = os.path.join(cfg.model_expe_folder.format(current_day))
+
+    if not os.path.exists(models_folder):
+        os.makedirs(models_folder)
+
+    model_filename = result_filename.replace('.csv', '.obj')
+    model_filepath = os.path.join(models_folder, model_filename)
+
+    # run `quest` expe
+    img_merge = run_quest_one_image(request, model_filepath, output_file)
+
+    if img_merge is not None:
+        # create output folder for tmp files if necessary
+        tmp_folder = os.path.join(settings.MEDIA_ROOT, cfg.output_tmp_folder)
+
+        if not os.path.exists(tmp_folder):
+            os.makedirs(tmp_folder)
+
+        # generate tmp merged image (pass as BytesIO was complicated..)
+        # TODO : add crontab task to erase generated img
+        filepath_img = os.path.join(tmp_folder, request.session.get('id') + '_' + scene_name + '' + expe_name + '.png')
+        img_merge.save(filepath_img)
 
-    print("here")
-    
     # expe parameters
     data = {
         'expe_name': expe_name,
@@ -121,5 +128,102 @@ def expe(request):
     return render(request, 'expe/expe.html', data)
 
 
-def run_quest_one_image():
-    pass
+def refresh_data(request, expe_name, scene_name):
+
+    request.session['expe'] = expe_name
+    request.session['scene'] = scene_name
+
+    request.session['begin'] = True
+    request.session['qualities'] = api.get_scene_qualities(scene_name)
+    # update unique timestamp each time new experience is launched
+    request.session['timestamp'] = datetime.strftime(datetime.utcnow(), "%Y-%m-%d_%Hh%Mm%Ss")
+
+    # TODO : add in cache ref_image
+    # get reference image
+    #ref_image = api.get_image(scene_name, 'max')
+    # save ref image as list (can't save python object)
+    #request.session['ref_img'] = np.array(ref_image).tolist()
+
+
+def run_quest_one_image(request, model_filepath, output_file):
+
+    # get parameters
+    qualities = request.session.get('qualities')
+    scene_name = request.session.get('scene')
+    # by default
+    iteration = 0
+
+    # first time only init `quest`
+    # if experience is started we can save data
+    if request.session.get('begin'):
+        answer = int(request.GET.get('answer'))
+        iteration = int(request.GET.get('iteration'))
+
+        answer_time = time.time() - request.session['answer_time']
+        previous_percentage = request.session.get('expe_percentage')
+        previous_orientation = request.session.get('expe_orientation')
+        previous_position = request.session.get('expe_percentage')
+        previous_entropy = request.session.get('expe_entropy')
+
+    # default params
+    max_iteration = 10
+    thresholds = np.arange(50, 10000, 50)
+    stim_space=np.asarray(qualities)
+    slopes = np.arange(0.0001, 0.001, 0.00003)
+
+    # check if necessary to construct `quest` object
+    if not os.path.exists(model_filepath):
+        qp = QuestPlus(stim_space, [thresholds, slopes], function=psychometric_fun)
+    else:
+        filehandler = open(model_filepath, 'rb') 
+        qp = pickle.load(filehandler)
+    
+    # construct image and update `quest` only if necessary
+    if iteration < max_iteration:
+        # process `quest`
+        next_stim = qp.next_contrast()
+        print(next_stim)
+
+        # construct new image
+        noisy_image = api.get_image(scene_name, next_stim)
+
+        # reconstruct reference image from list stored into session
+        ref_image = api.get_image(scene_name, 'max')
+        img_merge, percentage, orientation, position = crop_images(noisy_image, ref_image)
+    else:
+        return None
+    
+    # if experience is started we can save data
+    if request.session.get('begin'):
+
+        # TODO : check `i` variable 
+        # update of `quest`
+        # qp.update(qualities[i], answer)
+        qp.update(str(qualities[iteration]), answer) 
+        entropy = qp.get_entropy()
+
+        line = str(next_stim) 
+        line += ";" + scene_name 
+        line += ";" + str(previous_percentage)
+        line += ";" + str(previous_orientation) 
+        line += ";" + str(previous_orientation) 
+        line += ";" + str(answer) 
+        line += ";" + str(answer_time) 
+        line += ";" + str(entropy) 
+        line += '\n'
+        # TODO : add answer time from javascript
+        output_file.write(line)
+        output_file.flush()
+
+
+    # save `quest` model
+    file_pi = open(model_filepath, 'wb') 
+    pickle.dump(qp, file_pi)
+
+    # set current step data
+    request.session['expe_percentage'] = percentage
+    request.session['expe_orientation'] = orientation
+    request.session['expe_position'] = position
+    request.session['answer_time'] = time.time()
+
+    return img_merge

+ 4 - 0
static/css/expe.css

@@ -5,4 +5,8 @@ body {
 .container{
     margin-top: 2%;
     text-align: center;
+}
+
+#expeImg{
+    display: none;
 }

+ 73 - 22
static/js/keyEvents.js

@@ -1,29 +1,80 @@
 // implement `key` events
 document.onkeydown = checkKey;
 
+urlParams = new URLSearchParams(window.location.search);
+
+// Utils informations
+var host     = window.location.host
+var pathname = window.location.pathname
+var baseUrl  = location.protocol + "//" + host + pathname
+
+
+var KEYCODE_Q           = '81'
+var KEYCODE_ENTER       = '13'
+var KEYCODE_LEFT_ARROW  = '37'
+var KEYCODE_RIGHT_ARROW = '39'
+
+// get params if exists
+if (urlParams.has('scene')){
+
+   var scene = urlParams.get('scene')
+   var expe  = urlParams.get('expe')
+}
+
+console.log(scene)
+console.log(expe)
+
 function checkKey(e) {
 
-    e = e || window.event;
-
-
-    if (e.keyCode == '81') {
-        // `q` for quit expe
-       console.log('`q` key is pressed');
-       window.location = ''
-    }
-    else if (e.keyCode == '37') {
-       // left arrow
-       console.log('left arrow is pressed');
-    }
-    else if (e.keyCode == '39') {
-       // right arrow
-       console.log('right arrow is pressed');
-    }
-    else if (e.keyCode == '13') {
-       // right arrow
-       // TODO : avoid refresh with `enter` key when it is not necessary
-       console.log('right arrow is pressed');
-       window.location = window.location.href
-    }
+   e = e || window.event;
+
+   if (e.keyCode == '81') {
+      // `q` for quit expe
+      console.log('`q` key is pressed')
+      window.location = ''
+   }
+   else if (e.keyCode == '13') {
+
+      // check if experience is begin
+      if (!BEGIN_EXPE){
+
+         // right arrow
+         window.location = window.location.href + "&begin=true"
+      } 
+   }
+   else if (e.keyCode == '37' || e.keyCode == '39'){
+
+      // only do something is experience is begin
+      if (BEGIN_EXPE){
+         var answer;
+
+         // left arrow key
+         if (e.keyCode == '37'){
+            console.log('left arrow is pressed')
+            answer = '1'
+         }
+
+         // right arrow key
+         if (e.keyCode == '39'){
+            console.log('right arrow is pressed')
+            answer = '0'
+         }
+
+         var iteration = 0;
+
+         // update of iteration if exists
+         if (urlParams.has('iteration')){
+            iteration = urlParams.get('iteration')
+
+            // increment step
+            iteration++;
+         }
+
+         var params = "?scene=" + scene + "&expe=" + expe + "&begin=true&iteration=" + iteration + "&answer=" + answer
+         
+         console.log(baseUrl)
 
+         window.location = baseUrl + params
+      }
+   }
 }

+ 3 - 20
static/js/loadImg.js

@@ -3,24 +3,7 @@
 window.onload = function () {
     console.log('Load img here...');
 
-
-    /*img_data = document.getElementById('expeImg').getAttribute('data-img');
-    img_data = JSON.parse(img_data);
-
-    var p = new PNGlib(800, 800, 256); // construcor takes height, weight and color-depth
-    var background = p.color(0, 0, 0, 0); // set the background transparent
-
-    for (var i = 0; i < 800; i++){
-        for (var j = 0; j < 800; j++){
-
-            let r = img_data[i][j][0]
-            let g = img_data[i][j][1]
-            let b = img_data[i][j][2]
-
-            p.buffer[i, j] = p.color(r, g, b)
-        }
-    }
-    console.log('done')
-
-    document.getElementById('expeImg').src = "data:image/png;base64,"+p.getBase64();*/
+    setTimeout(function(){ 
+        document.getElementById("expeImg").style.display = "inline";
+    }, 500);
 }